Skip to content

Commit 23c6217

Browse files
Dev (#8)
* Add FastAPI server and visualization for SimpleAudit results - Implemented a FastAPI server in `server.py` to serve visualization pages and handle API requests for JSON audit results. - Created HTML template `visualizer.html` for displaying audit results with a responsive design using Tailwind CSS. - Added a favicon image `thumbnail.png` for the web application. - Introduced API endpoints to retrieve the file tree of JSON files and serve individual JSON file contents. - Implemented validation for JSON files to ensure they contain valid audit results. * Clarify local web server instructions in README Updated wording for clarity regarding the local web server. * Fix path resolution for HTML and favicon files in FastAPI server * Add package data for visualization assets in setuptools configuration * Bump version to 0.1.3 and update README with live demo link for visualization * Bump version from 0.1.3 to 0.1.4 in pyproject.toml * Refactor HTML structure in scenario viewer and add footer with PyPI link * Add footer with PyPI link and adjust main content structure in visualizer.html * Add URL handling for file loading and scenario selection in visualizer * Bump version from 0.1.4 to 0.1.5 in pyproject.toml * Fix indentation for version in pyproject.toml
1 parent 565c05d commit 23c6217

3 files changed

Lines changed: 102 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "simpleaudit"
7-
version = "0.1.4"
7+
version = "0.1.5"
88
description = "Lightweight AI Safety Auditing Framework"
99
readme = "README.md"
1010
license = {text = "MIT"}

simpleaudit/visualization/scenario_viewer.html

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
</style>
5858
</head>
5959

60-
<body class="bg-gray-50 text-gray-900 min-h-screen flex flex-col">
60+
<body class="bg-gray-50 text-gray-900 min-h-screen flex flex-col min-h-screen">
6161

6262
<!-- Header -->
6363
<header class="bg-white border-b border-gray-200 px-6 py-4 shadow-sm">
@@ -74,8 +74,10 @@ <h1 class="text-xl font-bold text-gray-800 flex items-center gap-2">
7474
</div>
7575
</header>
7676

77-
<!-- File Upload Section -->
78-
<div id="upload-section" class="max-w-7xl mx-auto w-full px-6 py-8">
77+
<!-- Main Content Wrapper for Flex Grow -->
78+
<main class="flex-1 flex flex-col">
79+
<!-- File Upload Section -->
80+
<div id="upload-section" class="max-w-7xl mx-auto w-full px-6 py-8">
7981
<div class="file-drop-zone rounded-xl p-12 text-center cursor-pointer hover:border-indigo-400 transition-all"
8082
id="drop-zone"
8183
onclick="document.getElementById('file-input').click()">
@@ -102,10 +104,10 @@ <h3 class="text-lg font-semibold text-gray-700 mb-2">Drop your JSON file here or
102104
Or try with sample data
103105
</button>
104106
</div>
105-
</div>
107+
</div>
106108

107-
<!-- Results View (Hidden initially) -->
108-
<div id="results-section" class="hidden flex-1 flex flex-col md:flex-row max-w-7xl mx-auto w-full px-6 pb-8 gap-6">
109+
<!-- Results View (Hidden initially) -->
110+
<div id="results-section" class="hidden flex-1 flex flex-col md:flex-row max-w-7xl mx-auto w-full px-6 pb-8 gap-6">
109111

110112
<!-- Sidebar / List View -->
111113
<div class="w-full md:w-1/3 flex flex-col bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden" id="scenario-list-container">
@@ -650,6 +652,12 @@ <h3 class="font-semibold text-indigo-900">Judge Feedback</h3>
650652
.replace(/'/g, "&#039;");
651653
}
652654
</script>
655+
656+
657+
</main>
658+
<footer class="w-full text-center py-4 bg-gray-50 border-t border-gray-200 mt-8">
659+
<a href="https://pypi.org/project/simpleaudit/" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:underline">simpleaudit on PyPI</a>
660+
</footer>
653661
</body>
654662

655663
</html>

simpleaudit/visualization/visualizer.html

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,9 @@
138138
</style>
139139
</head>
140140

141-
<body class="bg-gray-50 text-gray-900 h-screen flex flex-col overflow-hidden">
141+
<body class="bg-gray-50 text-gray-900 min-h-screen flex flex-col">
142142

143+
<main class="flex-1 flex flex-col">
143144
<!-- Header -->
144145
<header class="bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center shadow-sm z-10">
145146
<div class="flex items-center gap-3">
@@ -286,6 +287,33 @@ <h2 class="font-semibold text-gray-700 flex items-center gap-2">
286287
let sidebarWidth = 320; // Default width in pixels
287288
let expandedFolders = new Set(); // Track expanded folders
288289

290+
// On page load, check for file and scenario in URL
291+
window.addEventListener('DOMContentLoaded', () => {
292+
const params = new URLSearchParams(window.location.search);
293+
let file = params.get('case');
294+
if (file) {
295+
try {
296+
// decodeURIComponent twice to handle double encoding
297+
file = decodeURIComponent(decodeURIComponent(file));
298+
} catch (e) {
299+
file = decodeURIComponent(file);
300+
}
301+
const scenarioId = window.location.hash ? decodeURIComponent(window.location.hash.substring(1)) : null;
302+
if (file) {
303+
// Load the file, then select the scenario if present
304+
loadJsonFile(file, file).then(() => {
305+
if (scenarioId) {
306+
// Wait for scenarios to be loaded, then select
307+
setTimeout(() => {
308+
const found = allScenarios.find(s => s.id === scenarioId);
309+
if (found) selectScenario(found);
310+
}, 300);
311+
}
312+
});
313+
}
314+
}
315+
});
316+
289317
// Constants
290318
const SEVERITY_COLORS = {
291319
'pass': 'bg-severity-pass',
@@ -760,7 +788,21 @@ <h3 class="font-medium text-gray-900 truncate text-sm" title="${s.scenario_name
760788
selectedScenarioId = scenario.id;
761789
renderList();
762790
renderDetail(scenario);
763-
791+
// Update URL with ?case=...#scenario-id
792+
if (history.pushState) {
793+
const url = new URL(window.location);
794+
if (currentFilePath) {
795+
url.searchParams.set('case', encodeURIComponent(currentFilePath));
796+
}
797+
url.hash = `#${encodeURIComponent(scenario.id)}`;
798+
window.history.pushState({scenarioId: scenario.id, case: currentFilePath}, '', url);
799+
} else {
800+
let hash = `#${encodeURIComponent(scenario.id)}`;
801+
if (currentFilePath) {
802+
window.location.search = `?case=${encodeURIComponent(currentFilePath)}`;
803+
}
804+
window.location.hash = hash;
805+
}
764806
// Mobile: Show detail view full screen
765807
if (window.innerWidth < 768) {
766808
document.getElementById('detail-view').classList.add('has-selection');
@@ -828,8 +870,12 @@ <h2 class="text-2xl font-bold text-gray-900 leading-tight">${name}</h2>
828870

829871
if (history && history.length > 0) {
830872
html += `<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
831-
<div class="bg-gray-50 px-6 py-3 border-b border-gray-200">
873+
<div class="bg-gray-50 px-6 py-3 border-b border-gray-200 flex items-center justify-between">
832874
<h3 class="font-semibold text-gray-700">Conversation History</h3>
875+
<button onclick="copyCurrentUrl()" title="Copy link to this scenario" class="ml-2 px-2 py-1 text-xs bg-indigo-100 hover:bg-indigo-200 text-indigo-700 rounded transition-colors border border-indigo-200 flex items-center gap-1">
876+
<svg xmlns='http://www.w3.org/2000/svg' class='h-4 w-4' fill='none' viewBox='0 0 24 24' stroke='currentColor'><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 16h8a2 2 0 002-2V8a2 2 0 00-2-2H8a2 2 0 00-2 2v6a2 2 0 002 2zm0 0v2a2 2 0 002 2h4a2 2 0 002-2v-2' /></svg>
877+
Copy URL
878+
</button>
833879
</div>
834880
<div class="divide-y divide-gray-100">`;
835881

@@ -890,7 +936,45 @@ <h3 class="font-semibold text-indigo-900">Judge Feedback</h3>
890936
.replace(/'/g, "&#039;");
891937
}
892938

939+
// Copy current URL to clipboard
940+
function copyCurrentUrl() {
941+
const url = window.location.href;
942+
if (navigator.clipboard) {
943+
navigator.clipboard.writeText(url).then(() => {
944+
showCopyToast();
945+
});
946+
} else {
947+
// fallback
948+
const tempInput = document.createElement('input');
949+
tempInput.value = url;
950+
document.body.appendChild(tempInput);
951+
tempInput.select();
952+
document.execCommand('copy');
953+
document.body.removeChild(tempInput);
954+
showCopyToast();
955+
}
956+
}
957+
958+
// Show a toast message after copying
959+
function showCopyToast() {
960+
let toast = document.getElementById('copy-toast');
961+
if (!toast) {
962+
toast = document.createElement('div');
963+
toast.id = 'copy-toast';
964+
toast.className = 'fixed bottom-6 right-6 bg-indigo-600 text-white px-4 py-2 rounded shadow-lg z-50 text-sm';
965+
toast.textContent = 'Link copied!';
966+
document.body.appendChild(toast);
967+
}
968+
toast.style.display = 'block';
969+
setTimeout(() => { toast.style.display = 'none'; }, 1800);
970+
}
971+
893972
</script>
973+
974+
</main>
975+
<footer class="w-full text-center py-4 bg-gray-50 border-t border-gray-200">
976+
<a href="https://pypi.org/project/simpleaudit/" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:underline">simpleaudit on PyPI</a>
977+
</footer>
894978
</body>
895979

896980
</html>

0 commit comments

Comments
 (0)