ai-response-validator / ui /index.html
mbochniak01
Fix sentinel edge cases: hallucination combo guard + UI formatting
8d335e4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Response Validator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: #eef4fc;
color: #1a1a1a;
height: 100vh;
display: grid;
grid-template-rows: auto 1fr;
overflow: hidden;
}
/* ── Header ── */
header {
background: #fff;
border-bottom: 2px solid #1e3a5f;
padding: 14px 28px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}
.header-left h1 {
font-size: 22px;
font-weight: 900;
color: #1a1a1a;
letter-spacing: -0.5px;
}
.header-left h1 span { color: #3a6ea8; }
.header-left .tagline {
font-size: 11px;
color: #8aabcc;
margin-top: 2px;
}
/* ── Domain / Client switcher ── */
.switcher {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.switcher label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #8aabcc;
}
.btn-group {
display: flex;
border: 1px solid #c8dff5;
border-radius: 5px;
overflow: hidden;
}
.btn-group button {
background: #fff;
border: none;
border-right: 1px solid #c8dff5;
padding: 6px 14px;
font-size: 12px;
font-weight: 600;
color: #4a6a8a;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.btn-group button:last-child { border-right: none; }
.btn-group button.active {
background: #1e3a5f;
color: #fff;
}
.btn-group button:hover:not(.active) { background: #eef4fc; }
.divider-v {
width: 1px;
height: 28px;
background: #c8dff5;
}
/* ── Main layout ── */
main {
display: grid;
grid-template-columns: 1fr 360px;
overflow: hidden;
}
/* ── Chat panel ── */
.chat-panel {
display: flex;
flex-direction: column;
border-right: 1px solid #c8dff5;
overflow: hidden;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 16px;
}
.message {
display: flex;
flex-direction: column;
gap: 4px;
max-width: 80%;
}
.message.user { align-self: flex-end; }
.message.bot { align-self: flex-start; }
.message .bubble {
padding: 12px 16px;
border-radius: 8px;
font-size: 13.5px;
line-height: 1.6;
}
.message.user .bubble {
background: #1e3a5f;
color: #fff;
border-radius: 8px 8px 2px 8px;
}
.message.bot .bubble {
background: #fff;
color: #1a1a1a;
border: 1px solid #c8dff5;
border-radius: 8px 8px 8px 2px;
}
.message .meta {
font-size: 10px;
color: #8aabcc;
padding: 0 4px;
}
.message.user .meta { text-align: right; }
/* overall pass/fail badge on bot message */
.verdict {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 3px;
margin-top: 4px;
align-self: flex-start;
}
.verdict.pass { background: #f1f8f1; color: #2e7d32; border: 1px solid #c8e6c9; }
.verdict.fail { background: #fdf1f1; color: #c62828; border: 1px solid #ffcdd2; }
.verdict.warn { background: #fffbf0; color: #a06000; border: 1px solid #ffe082; }
/* ── Input bar ── */
.input-bar {
padding: 16px 28px;
background: #fff;
border-top: 1px solid #c8dff5;
display: flex;
gap: 10px;
}
.input-bar input {
flex: 1;
padding: 10px 14px;
border: 1px solid #c8dff5;
border-radius: 6px;
font-size: 13.5px;
font-family: 'Inter', sans-serif;
outline: none;
transition: border-color 0.15s;
}
.input-bar input:focus { border-color: #3a6ea8; }
.input-bar input:disabled { background: #f5f9ff; color: #8aabcc; }
.input-bar button {
padding: 10px 20px;
background: #1e3a5f;
color: #fff;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 700;
cursor: pointer;
transition: background 0.15s;
white-space: nowrap;
}
.input-bar button:hover:not(:disabled) { background: #3a6ea8; }
.input-bar button:disabled { background: #93b8d8; cursor: not-allowed; }
.example-chips {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 10px;
}
.example-chip {
background: #eef4fc;
border: 1px solid #c5d8f0;
border-radius: 6px;
padding: 7px 12px;
font-size: 12px;
color: #1e3a5f;
text-align: left;
cursor: pointer;
transition: background 0.15s;
}
.example-chip:hover {
background: #d6e8fa;
}
.out-of-scope-badge {
display: inline-block;
background: #fff3e0;
color: #e65100;
border: 1px solid #ffcc80;
border-radius: 3px;
font-size: 11px;
font-weight: 700;
padding: 1px 6px;
margin-right: 6px;
vertical-align: middle;
letter-spacing: 0.02em;
}
.flagged-banner {
background: #fff0f0;
border-left: 4px solid #e74c3c;
color: #c0392b;
font-size: 12px;
font-weight: 600;
padding: 8px 16px;
margin: 12px 16px 0;
border-radius: 4px;
}
/* ── Eval panel ── */
.eval-panel {
background: #fff;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.eval-panel .panel-header {
padding: 16px 20px 12px;
border-bottom: 1px solid #e8f2ff;
font-size: 10px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 2px;
color: #8aabcc;
position: sticky;
top: 0;
background: #fff;
z-index: 1;
}
.eval-empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
color: #b0cce8;
padding: 40px 20px;
text-align: center;
}
.eval-empty .icon { font-size: 36px; }
.eval-empty p { font-size: 12px; line-height: 1.6; }
.eval-content { padding: 16px 20px; display: flex; flex-direction: column; gap: 20px; }
/* Metric card */
.metric-card {
border: 1px solid #e0eef8;
border-left: 3px solid #1e3a5f;
border-radius: 0 6px 6px 0;
padding: 12px 14px;
background: #f5f9ff;
}
.metric-card.pass { border-left-color: #4caf50; background: #f0faf3; }
.metric-card.fail { border-left-color: #c62828; background: #fdf5f5; }
.metric-card.warn { border-left-color: #f9a825; background: #fffdf0; }
.metric-card .metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.metric-card .metric-name {
font-size: 12px;
font-weight: 800;
color: #1e3a5f;
font-family: 'JetBrains Mono', monospace;
}
.metric-card.pass .metric-name { color: #2e7d32; }
.metric-card.fail .metric-name { color: #c62828; }
.score-badge {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 700;
padding: 2px 8px;
border-radius: 3px;
border: 1px solid;
}
.score-badge.pass { background: #f1f8f1; color: #2e7d32; border-color: #c8e6c9; }
.score-badge.fail { background: #fdf1f1; color: #c62828; border-color: #ffcdd2; }
.score-badge.warn { background: #fffbf0; color: #a06000; border-color: #ffe082; }
.metric-card .metric-detail {
font-size: 11px;
color: #4a6080;
line-height: 1.5;
}
/* Score bar */
.score-bar-wrap { margin-top: 8px; }
.score-bar-bg {
height: 4px;
background: #e0eef8;
border-radius: 2px;
overflow: hidden;
}
.score-bar-fill {
height: 100%;
border-radius: 2px;
transition: width 0.4s ease;
}
.score-bar-fill.pass { background: #4caf50; }
.score-bar-fill.fail { background: #c62828; }
.score-bar-fill.warn { background: #f9a825; }
/* Sources */
.sources-section .sources-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #8aabcc;
margin-bottom: 8px;
}
.source-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 7px 10px;
background: #f5f9ff;
border: 1px solid #e0eef8;
border-radius: 5px;
margin-bottom: 5px;
font-size: 11.5px;
}
.source-item .source-title { color: #2a4a6a; font-weight: 500; }
.source-item .source-score {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: #8aabcc;
}
/* Thinking indicator */
.thinking {
display: flex;
gap: 5px;
align-items: center;
padding: 12px 16px;
background: #fff;
border: 1px solid #c8dff5;
border-radius: 8px 8px 8px 2px;
width: fit-content;
}
.thinking span {
width: 7px; height: 7px;
background: #3a6ea8;
border-radius: 50%;
animation: bounce 1.2s infinite ease-in-out;
}
.thinking span:nth-child(2) { animation-delay: 0.2s; }
.thinking span:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
/* Scrollbar */
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #c8dff5; border-radius: 3px; }
</style>
</head>
<body>
<header>
<div class="header-left">
<h1>AI Response <span>Validator</span></h1>
<div class="tagline">Domain-agnostic RAG evaluation · real-time L1 metrics · RosettaStone terminology</div>
</div>
<div class="switcher">
<label>Domain</label>
<div class="btn-group" id="domain-switcher"></div>
<div class="divider-v"></div>
<label>Client</label>
<div class="btn-group" id="client-switcher"></div>
</div>
</header>
<main>
<div class="chat-panel">
<div class="messages" id="messages">
<!-- populated by app.js -->
</div>
<div class="input-bar">
<input
type="text"
id="query-input"
placeholder="Ask something…"
autocomplete="off"
/>
<button id="send-btn">Send</button>
</div>
</div>
<div class="eval-panel">
<div class="panel-header">Evaluation</div>
<div id="eval-body">
<div class="eval-empty">
<div class="icon"></div>
<p>Send a message to see<br>real-time metric evaluation.</p>
</div>
</div>
</div>
</main>
<script src="/static/app.js"></script>
</body>
</html>