trimmer / index.html
victor's picture
victor HF Staff
Revert trim indicator to simple ellipsis
09e5623
raw
history blame
16.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Message Trimmer Demo - WildChat Conversations</title>
<style>
:root {
--bg: #0b0b0b;
--surface: #111111;
--surface-2: #141414;
--text: #eaeaea;
--muted: #9b9b9b;
--border: #1f1f1f;
--accent: #8ab4f8; /* subtle blue */
--radius: 12px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: var(--bg);
color: var(--text);
padding: 24px;
min-height: 100vh;
line-height: 1.45;
}
.container {
max-width: 1100px;
margin: 0 auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
header {
background: transparent;
border-bottom: 1px solid var(--border);
padding: 28px 30px;
text-align: center;
}
h1 { font-size: 1.75em; font-weight: 700; letter-spacing: 0.2px; }
.subtitle { color: var(--muted); font-size: 0.95em; margin-top: 6px; }
.controls {
padding: 24px 30px;
background: var(--surface-2);
border-bottom: 1px solid var(--border);
}
.control-group { margin-bottom: 18px; }
.control-group:last-child { margin-bottom: 0; }
label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: var(--muted);
font-size: 0.9em;
}
input[type="range"] {
width: 100%;
height: 4px;
border-radius: 2px;
background: #222;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 16px; height: 16px; border-radius: 50%;
background: var(--accent);
cursor: pointer;
border: 2px solid #0d0d0d;
box-shadow: 0 0 0 2px rgba(138,180,248,0.15);
}
input[type="range"]::-moz-range-thumb {
width: 16px; height: 16px; border-radius: 50%;
background: var(--accent);
cursor: pointer; border: 2px solid #0d0d0d;
}
.value-display {
display: inline-block;
color: var(--accent);
padding: 2px 10px;
border-radius: 999px;
font-size: 0.85em; font-weight: 600;
margin-left: 10px;
border: 1px solid var(--border);
background: #0e0e0e;
}
select {
width: 100%; padding: 10px 12px;
border: 1px solid var(--border); border-radius: 8px;
font-size: 0.95em; background: #0f0f0f; color: var(--text);
cursor: pointer;
}
select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(138,180,248,0.15); }
.conversation { padding: 26px 30px; }
.message { margin-bottom: 28px; animation: fadeIn 0.25s ease-in; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px);} to { opacity: 1; transform: translateY(0);} }
.message-header { display: flex; align-items: center; margin-bottom: 10px; gap: 10px; }
.role-badge {
padding: 6px 12px; border-radius: 999px; font-size: 0.72em; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.5px; background: #0f0f0f; color: var(--muted);
border: 1px solid var(--border);
}
.char-count { font-size: 0.75em; color: var(--muted); font-weight: 500; }
.message-content {
background: var(--surface-2);
padding: 18px; border-radius: 10px; border-left: 3px solid var(--border);
}
.message-user .message-content { border-left-color: #2a2a2a; }
.message-assistant .message-content { border-left-color: var(--accent); }
.original { margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px dashed var(--border); }
.trimmed, .no-trim {
background: var(--surface);
padding: 14px; border-radius: 8px; border: 1px solid var(--border);
}
.trimmed { border-color: rgba(138,180,248,0.35); }
.section-label {
font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
color: var(--muted); margin-bottom: 8px;
}
.trimmed .section-label { color: var(--accent); }
.content-text { line-height: 1.6; color: var(--text); white-space: pre-wrap; word-wrap: break-word; }
.stats {
margin-top: 10px; padding: 10px; border-radius: 6px; font-size: 0.8em; color: var(--muted);
background: #0f0f0f; border: 1px solid var(--border);
}
.stats strong { color: var(--text); }
.reduction { color: var(--accent); font-weight: 600; }
.code-reference {
background: #0f0f0f; border: 1px solid var(--border); border-radius: 8px; padding: 18px; margin: 20px 30px;
font-family: 'Monaco', 'Menlo', monospace; font-size: 0.85em; line-height: 1.6; color: var(--text);
}
.code-reference h3 { margin-bottom: 12px; color: var(--muted); font-size: 1em; font-weight: 600; }
.code-line { color: var(--text); margin: 4px 0; }
.comment { color: #7a7a7a; }
button {
background: #0f0f0f; color: var(--text); border: 1px solid var(--border);
padding: 10px 16px; border-radius: 8px; cursor: pointer; font-size: 0.9em; font-weight: 600; margin-top: 10px;
}
button:hover { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(138,180,248,0.15); }
.info-box {
background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--accent);
border-radius: 8px; padding: 18px; margin: 20px 30px;
}
.info-box h3 { color: var(--text); margin-bottom: 8px; font-size: 1em; }
.info-box p { color: var(--muted); line-height: 1.6; font-size: 0.9em; }
/* Focus styles */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>Message Trimmer Demo</h1>
<p class="subtitle">Demonstrating trim functions on real WildChat-1M conversations</p>
</header>
<div class="info-box">
<h3>About This Demo</h3>
<p>
This demo shows how the <strong>trimMiddle()</strong> function works in the LLM router architecture.
The function trims messages by keeping the start (60%) and end (40%) with an ellipsis in between,
reducing token count while preserving context for intent classification.
Adjust the max length sliders below to see how different messages are trimmed.
</p>
</div>
<div class="controls">
<div class="control-group">
<label>
Max Assistant Message Length: <span class="value-display" id="assistantLengthValue">500</span>
</label>
<input type="range" id="assistantLength" min="50" max="1000" value="500" step="10">
</div>
<div class="control-group">
<label>
Max Previous User Message Length: <span class="value-display" id="userLengthValue">400</span>
</label>
<input type="range" id="userLength" min="50" max="1000" value="400" step="10">
</div>
<div class="control-group">
<label>Select Conversation:</label>
<select id="conversationSelect">
<option value="0">Loading conversations...</option>
</select>
</div>
</div>
<div class="code-reference">
<h3>Implementation (from src/lib/server/router/arch.ts)</h3>
<div class="code-line"><span class="comment">// Trim assistant messages to reduce routing prompt size</span></div>
<div class="code-line">if (m.role === "assistant") {</div>
<div class="code-line">&nbsp;&nbsp;return { ...m, content: trimMiddle(m.content, maxAssistantLength) };</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line"><span class="comment">// Trim previous user messages (latest preserved)</span></div>
<div class="code-line">if (m.role === "user" && idx !== lastUserIndex) {</div>
<div class="code-line">&nbsp;&nbsp;return { ...m, content: trimMiddle(m.content, maxPrevUserLength) };</div>
<div class="code-line">}</div>
</div>
<div id="conversationDisplay" class="conversation">
<p style="text-align: center; color: var(--muted);">Loading conversations...</p>
</div>
</div>
<script>
// Implementation of trimMiddle function from arch.ts
function trimMiddle(content, maxLength) {
if (content.length <= maxLength) return content;
const indicator = "…";
const availableLength = maxLength - indicator.length;
if (availableLength <= 0) {
return content.slice(0, maxLength);
}
// Reserve more space for the start (60%)
const startLength = Math.ceil(availableLength * 0.6);
const endLength = availableLength - startLength;
if (endLength <= 0) {
return content.slice(0, availableLength) + indicator;
}
const start = content.slice(0, startLength);
const end = content.slice(-endLength);
return start + indicator + end;
}
// Fetch and store conversations
let conversations = [];
async function loadConversations() {
try {
const response = await fetch('https://datasets-server.huggingface.co/rows?dataset=allenai%2FWildChat-1M&config=default&split=train&offset=0&length=100');
const data = await response.json();
// Filter for conversations with multiple turns
conversations = data.rows
.filter(row => row.row.conversation && row.row.conversation.length >= 2)
.map(row => row.row.conversation)
.slice(0, 20); // Take first 20 conversations
populateConversationSelect();
displayConversation(0);
} catch (error) {
console.error('Error loading conversations:', error);
document.getElementById('conversationDisplay').innerHTML =
'<p style="text-align: center; color: var(--accent);">Error loading conversations. Please refresh the page.</p>';
}
}
function populateConversationSelect() {
const select = document.getElementById('conversationSelect');
select.innerHTML = conversations.map((conv, idx) => {
const preview = conv[0].content.slice(0, 60).replace(/\n/g, ' ');
return `<option value="${idx}">Conversation ${idx + 1}: ${preview}...</option>`;
}).join('');
}
function displayConversation(index) {
const conversation = conversations[index];
if (!conversation) return;
const assistantLength = parseInt(document.getElementById('assistantLength').value);
const userLength = parseInt(document.getElementById('userLength').value);
const lastUserIndex = conversation.map((m, idx) => ({ idx, role: m.role }))
.reverse()
.find(m => m.role === 'user')?.idx ?? -1;
let html = '';
conversation.forEach((message, idx) => {
const isUser = message.role === 'user';
const isLastUser = idx === lastUserIndex;
const content = message.content;
let trimmed = content;
let shouldTrim = false;
// Apply trimming logic
if (message.role === 'assistant') {
trimmed = trimMiddle(content, assistantLength);
shouldTrim = content.length > assistantLength;
} else if (message.role === 'user' && !isLastUser) {
trimmed = trimMiddle(content, userLength);
shouldTrim = content.length > userLength;
}
const reductionPercent = shouldTrim ?
(((content.length - trimmed.length) / content.length) * 100).toFixed(1) : 0;
html += `
<div class="message message-${message.role}">
<div class="message-header">
<span class="role-badge role-${message.role}">${message.role}</span>
<span class="char-count">${content.length} characters</span>
${isLastUser ? '<span class="char-count" style="color: var(--accent); font-weight: 600;">★ Latest user message · preserved</span>' : ''}
</div>
<div class="message-content">
<div class="original">
<div class="section-label">Original</div>
<div class="content-text">${escapeHtml(content)}</div>
</div>
<div class="${shouldTrim ? 'trimmed' : 'no-trim'}">
<div class="section-label">${shouldTrim ? 'Trimmed for Router' : 'No Trimming Required'}</div>
<div class="content-text">${escapeHtml(trimmed)}</div>
${shouldTrim ? `
<div class="stats">
<strong>Original:</strong> ${content.length} chars |
<strong>Trimmed:</strong> ${trimmed.length} chars |
<span class="reduction">Reduced by ${reductionPercent}%</span>
</div>
` : ''}
</div>
</div>
</div>
`;
});
document.getElementById('conversationDisplay').innerHTML = html;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Event listeners
document.getElementById('assistantLength').addEventListener('input', (e) => {
document.getElementById('assistantLengthValue').textContent = e.target.value;
const selectedIndex = parseInt(document.getElementById('conversationSelect').value);
displayConversation(selectedIndex);
});
document.getElementById('userLength').addEventListener('input', (e) => {
document.getElementById('userLengthValue').textContent = e.target.value;
const selectedIndex = parseInt(document.getElementById('conversationSelect').value);
displayConversation(selectedIndex);
});
document.getElementById('conversationSelect').addEventListener('change', (e) => {
displayConversation(parseInt(e.target.value));
});
// Initialize
loadConversations();
</script>
</body>
</html>