infinite-agents-public/claude_code_devtools/claude_devtool_6.html

929 lines
33 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Transcript Search - Claude Code DevTools</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-tertiary: #21262d;
--border-color: #30363d;
--text-primary: #c9d1d9;
--text-secondary: #8b949e;
--text-muted: #6e7681;
--accent-blue: #58a6ff;
--accent-green: #3fb950;
--accent-purple: #bc8cff;
--accent-orange: #ff9966;
--accent-red: #f85149;
--shadow: rgba(0, 0, 0, 0.4);
}
body {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}
header {
background: var(--bg-secondary);
padding: 2rem;
border-bottom: 1px solid var(--border-color);
box-shadow: 0 4px 8px var(--shadow);
}
h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
color: var(--accent-blue);
}
.tagline {
color: var(--text-secondary);
font-size: 0.95rem;
}
main {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.search-interface {
background: var(--bg-secondary);
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
border: 1px solid var(--border-color);
}
.search-box {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
input[type="text"] {
flex: 1;
padding: 0.75rem 1rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-primary);
font-family: inherit;
font-size: 0.95rem;
}
input[type="text"]:focus {
outline: none;
border-color: var(--accent-blue);
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.1);
}
button {
padding: 0.75rem 1.5rem;
background: var(--accent-blue);
border: none;
border-radius: 6px;
color: #000;
font-weight: 600;
cursor: pointer;
font-family: inherit;
transition: background 0.2s;
}
button:hover {
background: #6cb6ff;
}
button.secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
button.secondary:hover {
background: var(--border-color);
}
.filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-group label {
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 600;
}
select {
padding: 0.5rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-primary);
font-family: inherit;
}
.stats-bar {
background: var(--bg-secondary);
border-radius: 8px;
padding: 1rem 2rem;
margin-bottom: 2rem;
border: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.stat {
display: flex;
align-items: center;
gap: 0.5rem;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.85rem;
}
.stat-value {
color: var(--accent-green);
font-weight: 700;
font-size: 1.1rem;
}
#results {
min-height: 200px;
}
.result-item {
background: var(--bg-secondary);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
border: 1px solid var(--border-color);
opacity: 0;
transform: translateY(20px);
transition: all 0.3s ease;
}
.result-item.visible {
opacity: 1;
transform: translateY(0);
}
.result-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.result-meta {
display: flex;
gap: 1rem;
align-items: center;
}
.role-badge {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.role-user {
background: rgba(88, 166, 255, 0.2);
color: var(--accent-blue);
}
.role-assistant {
background: rgba(63, 185, 80, 0.2);
color: var(--accent-green);
}
.timestamp {
color: var(--text-muted);
font-size: 0.85rem;
}
.score-container {
display: flex;
align-items: center;
gap: 0.5rem;
}
.score-bar-bg {
width: 100px;
height: 8px;
background: var(--bg-tertiary);
border-radius: 4px;
overflow: hidden;
}
.score-bar {
height: 100%;
background: linear-gradient(90deg, var(--accent-orange), var(--accent-green));
transition: width 0.5s ease;
}
.score-text {
font-size: 0.85rem;
color: var(--accent-green);
font-weight: 600;
}
.result-content {
color: var(--text-primary);
line-height: 1.8;
margin-bottom: 1rem;
}
.highlight {
background: rgba(255, 153, 102, 0.3);
color: var(--accent-orange);
padding: 2px 4px;
border-radius: 3px;
font-weight: 600;
}
.result-tools {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.tool-badge {
padding: 0.25rem 0.5rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.75rem;
color: var(--accent-purple);
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: var(--text-muted);
}
.empty-state svg {
width: 64px;
height: 64px;
margin-bottom: 1rem;
opacity: 0.5;
}
.docs {
background: var(--bg-secondary);
border-radius: 8px;
padding: 2rem;
margin-top: 3rem;
border: 1px solid var(--border-color);
}
.docs h2 {
color: var(--accent-blue);
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
.docs h3 {
color: var(--accent-purple);
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.docs ul, .docs ol {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.docs li {
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
.docs strong {
color: var(--text-primary);
}
.docs a {
color: var(--accent-blue);
text-decoration: none;
}
.docs a:hover {
text-decoration: underline;
}
.doc-content {
color: var(--text-secondary);
}
footer {
text-align: center;
padding: 2rem;
color: var(--text-muted);
border-top: 1px solid var(--border-color);
margin-top: 4rem;
}
footer a {
color: var(--accent-blue);
text-decoration: none;
}
.keyboard-hint {
font-size: 0.8rem;
color: var(--text-muted);
margin-top: 0.5rem;
}
kbd {
background: var(--bg-tertiary);
padding: 0.2rem 0.5rem;
border-radius: 3px;
border: 1px solid var(--border-color);
font-family: inherit;
font-size: 0.85em;
}
@media (max-width: 768px) {
.filters {
grid-template-columns: 1fr;
}
.stats-bar {
flex-direction: column;
gap: 1rem;
}
.result-header {
flex-direction: column;
gap: 1rem;
}
}
</style>
</head>
<body>
<header>
<h1>Advanced Transcript Search</h1>
<p class="tagline">Fuzzy search across Claude Code transcripts with D3.js-powered visualizations</p>
</header>
<main>
<section class="search-interface">
<div class="search-box">
<input
type="text"
id="searchInput"
placeholder="Search messages, code, tool calls... (fuzzy matching enabled)"
autofocus
>
<button onclick="performSearch()">Search</button>
<button class="secondary" onclick="clearSearch()">Clear</button>
</div>
<div class="filters">
<div class="filter-group">
<label for="roleFilter">Role</label>
<select id="roleFilter" onchange="performSearch()">
<option value="all">All Roles</option>
<option value="user">User</option>
<option value="assistant">Assistant</option>
</select>
</div>
<div class="filter-group">
<label for="dateFilter">Date Range</label>
<select id="dateFilter" onchange="performSearch()">
<option value="all">All Time</option>
<option value="today">Today</option>
<option value="week">Last 7 Days</option>
<option value="month">Last 30 Days</option>
</select>
</div>
<div class="filter-group">
<label for="toolFilter">Tools Used</label>
<select id="toolFilter" onchange="performSearch()">
<option value="all">All Tools</option>
<option value="Read">Read</option>
<option value="Write">Write</option>
<option value="Bash">Bash</option>
<option value="Edit">Edit</option>
<option value="Grep">Grep</option>
</select>
</div>
<div class="filter-group">
<label for="scoreThreshold">Min Score</label>
<select id="scoreThreshold" onchange="performSearch()">
<option value="0">0% - All Results</option>
<option value="25">25% - Weak Match</option>
<option value="50" selected>50% - Good Match</option>
<option value="75">75% - Strong Match</option>
<option value="90">90% - Exact Match</option>
</select>
</div>
</div>
<div class="keyboard-hint">
<kbd>Ctrl</kbd> + <kbd>K</kbd> to focus search • <kbd>Enter</kbd> to search • <kbd>Esc</kbd> to clear
</div>
</section>
<div class="stats-bar" id="statsBar" style="display: none;">
<div class="stat">
<span class="stat-label">Results:</span>
<span class="stat-value" id="resultCount">0</span>
</div>
<div class="stat">
<span class="stat-label">Avg Score:</span>
<span class="stat-value" id="avgScore">0%</span>
</div>
<div class="stat">
<span class="stat-label">Search Time:</span>
<span class="stat-value" id="searchTime">0ms</span>
</div>
</div>
<div id="results"></div>
<section class="docs">
<h2>About This Tool</h2>
<div class="doc-content">
<h3>Purpose</h3>
<p>Advanced search interface for Claude Code JSONL transcripts featuring fuzzy matching, score-based ranking, and D3.js-powered result visualization. Enables developers to quickly find relevant conversations, code snippets, and tool usage patterns across coding sessions.</p>
<h3>Features</h3>
<ul>
<li><strong>Fuzzy Search Algorithm:</strong> Tolerates typos and approximate matches using Levenshtein distance</li>
<li><strong>D3.js Data Binding:</strong> Dynamic result rendering with smooth enter/exit transitions</li>
<li><strong>Linear Scale Visualization:</strong> Score bars showing match relevance from 0-100%</li>
<li><strong>Multi-Facet Filtering:</strong> Filter by role, date range, tools, and minimum score threshold</li>
<li><strong>Context Highlighting:</strong> Matching terms highlighted in result snippets</li>
<li><strong>Score-Based Ranking:</strong> Results sorted by relevance with visual indicators</li>
<li><strong>Keyboard Navigation:</strong> Power-user shortcuts for efficient searching</li>
<li><strong>Real-Time Statistics:</strong> Live result count, average score, and search performance metrics</li>
</ul>
<h3>Web Research Integration</h3>
<p><strong>Source:</strong> <a href="https://d3js.org/getting-started" target="_blank">D3.js Getting Started Guide</a></p>
<p><strong>Techniques Applied:</strong></p>
<ul>
<li><strong>D3 Data Joining & Selection:</strong> Using selectAll().data().join() pattern for efficient DOM updates when search results change</li>
<li><strong>Linear Scale Functions:</strong> Implemented d3.scaleLinear() to map relevance scores (0-100) to visual bar widths, creating intuitive score visualization</li>
<li><strong>Smooth Transitions:</strong> Applied D3 transition() with duration and delay for staggered result animations, creating polished user experience</li>
</ul>
<h3>Usage</h3>
<ol>
<li>Type your search query in the search box (fuzzy matching automatically enabled)</li>
<li>Optionally apply filters for role, date range, tools, or minimum score</li>
<li>Press Enter or click Search to execute</li>
<li>Results appear with relevance scores shown as visual bars</li>
<li>Matching terms are highlighted in context snippets</li>
<li>Use keyboard shortcuts for faster workflow: Ctrl+K to focus, Esc to clear</li>
</ol>
<h3>Sample Data</h3>
<p>This tool includes realistic sample transcript data demonstrating various Claude Code interactions including file operations, git commands, web research, and code generation. In production, load your actual JSONL transcript files.</p>
<h3>D3.js Integration Details</h3>
<p><strong>Why D3.js for Search Results?</strong></p>
<p>Traditional DOM manipulation creates jerky, unnatural result updates. D3's data-binding approach enables:</p>
<ul>
<li><strong>Declarative Updates:</strong> Define what should exist based on data, D3 handles the transitions</li>
<li><strong>Enter/Exit Patterns:</strong> Smooth animations when results appear or disappear</li>
<li><strong>Scale Functions:</strong> Automatic mapping of data ranges to visual properties</li>
<li><strong>Performance:</strong> Efficient DOM updates only where data changed</li>
</ul>
<h3>Fuzzy Search Algorithm</h3>
<p>Implements simplified Levenshtein distance calculation for typo tolerance. Scores based on:</p>
<ul>
<li>Character-by-character matching (case-insensitive)</li>
<li>Sequence matching bonuses</li>
<li>Position weighting (earlier matches score higher)</li>
<li>Normalized to 0-100 scale for consistency</li>
</ul>
</div>
</section>
</main>
<footer>
<p>Claude Code DevTools | Iteration 6 - D3.js Search Interface</p>
<p>Web Source: <a href="https://d3js.org/getting-started" target="_blank">https://d3js.org/getting-started</a></p>
<p style="margin-top: 0.5rem; font-size: 0.85rem;">Generated via web-enhanced infinite loop • D3.js v7</p>
</footer>
<!-- D3.js Library -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// Sample transcript data for demonstration
const sampleTranscriptData = [
{
uuid: "msg-001",
timestamp: new Date(Date.now() - 3600000).toISOString(),
role: "user",
content: "Can you help me implement a fuzzy search algorithm in JavaScript?",
tools: []
},
{
uuid: "msg-002",
timestamp: new Date(Date.now() - 3500000).toISOString(),
role: "assistant",
content: "I'll help you implement a fuzzy search algorithm using Levenshtein distance. This allows matching even with typos.",
tools: ["Read", "Write"]
},
{
uuid: "msg-003",
timestamp: new Date(Date.now() - 3000000).toISOString(),
role: "user",
content: "How do I visualize search results with D3.js?",
tools: []
},
{
uuid: "msg-004",
timestamp: new Date(Date.now() - 2900000).toISOString(),
role: "assistant",
content: "D3.js provides powerful data binding for visualizations. I'll show you how to use scales and transitions for search result bars.",
tools: ["WebFetch", "Write"]
},
{
uuid: "msg-005",
timestamp: new Date(Date.now() - 2400000).toISOString(),
role: "user",
content: "Can you add highlighting for matched terms in the search results?",
tools: []
},
{
uuid: "msg-006",
timestamp: new Date(Date.now() - 2300000).toISOString(),
role: "assistant",
content: "I'll implement text highlighting by wrapping matched terms in span elements with a highlight class. This makes search results easier to scan.",
tools: ["Edit"]
},
{
uuid: "msg-007",
timestamp: new Date(Date.now() - 1800000).toISOString(),
role: "user",
content: "How can I filter results by role and date range?",
tools: []
},
{
uuid: "msg-008",
timestamp: new Date(Date.now() - 1700000).toISOString(),
role: "assistant",
content: "I'll add filter functions that combine with the search algorithm. The filters will work together to narrow results by role, date, and tool usage.",
tools: ["Edit", "Read"]
},
{
uuid: "msg-009",
timestamp: new Date(Date.now() - 1200000).toISOString(),
role: "user",
content: "What's the best way to score search relevance?",
tools: []
},
{
uuid: "msg-010",
timestamp: new Date(Date.now() - 1100000).toISOString(),
role: "assistant",
content: "For search relevance scoring, we can use a combination of fuzzy matching score, term frequency, and position weighting. Higher scores for exact matches and earlier positions.",
tools: []
},
{
uuid: "msg-011",
timestamp: new Date(Date.now() - 600000).toISOString(),
role: "user",
content: "Show me how to implement smooth animations when results update",
tools: []
},
{
uuid: "msg-012",
timestamp: new Date(Date.now() - 500000).toISOString(),
role: "assistant",
content: "D3's transition() function creates smooth animations. I'll add staggered delays so results appear sequentially with fade-in and slide-up effects.",
tools: ["Edit", "Write"]
},
{
uuid: "msg-013",
timestamp: new Date(Date.now() - 300000).toISOString(),
role: "user",
content: "Can you add keyboard shortcuts like Ctrl+K for search focus?",
tools: []
},
{
uuid: "msg-014",
timestamp: new Date(Date.now() - 200000).toISOString(),
role: "assistant",
content: "I'll implement keyboard event listeners for power-user shortcuts: Ctrl+K to focus search, Enter to execute, and Esc to clear.",
tools: ["Edit"]
},
{
uuid: "msg-015",
timestamp: new Date(Date.now() - 60000).toISOString(),
role: "user",
content: "How do I export search results to a file?",
tools: []
},
{
uuid: "msg-016",
timestamp: new Date(Date.now() - 30000).toISOString(),
role: "assistant",
content: "I can add an export function that converts results to JSON or CSV format and triggers a browser download using the File API.",
tools: ["Grep", "Bash", "Write"]
}
];
let transcriptData = sampleTranscriptData;
let currentResults = [];
// D3.js Scale for score visualization
const scoreScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 100]); // Percentage width
// Fuzzy search algorithm using simplified Levenshtein distance
function fuzzyMatch(query, text) {
if (!query || !text) return 0;
query = query.toLowerCase();
text = text.toLowerCase();
// Exact match bonus
if (text.includes(query)) return 100;
let score = 0;
let queryIndex = 0;
let consecutiveMatches = 0;
for (let i = 0; i < text.length && queryIndex < query.length; i++) {
if (text[i] === query[queryIndex]) {
score += 10;
queryIndex++;
consecutiveMatches++;
// Bonus for consecutive matches
if (consecutiveMatches > 1) {
score += consecutiveMatches * 2;
}
} else {
consecutiveMatches = 0;
}
}
// Penalty for incomplete match
if (queryIndex < query.length) {
score -= (query.length - queryIndex) * 5;
}
// Normalize to 0-100
score = Math.max(0, Math.min(100, score));
return score;
}
// Highlight matching terms in text
function highlightMatches(text, query) {
if (!query) return text;
const regex = new RegExp(`(${query.split('').join('.*?')})`, 'gi');
return text.replace(regex, '<span class="highlight">$1</span>');
}
// Filter by date range
function filterByDate(timestamp, range) {
if (range === 'all') return true;
const msgDate = new Date(timestamp);
const now = new Date();
const diff = now - msgDate;
switch(range) {
case 'today':
return diff < 86400000; // 24 hours
case 'week':
return diff < 604800000; // 7 days
case 'month':
return diff < 2592000000; // 30 days
default:
return true;
}
}
// Main search function
function performSearch() {
const startTime = performance.now();
const query = document.getElementById('searchInput').value.trim();
const roleFilter = document.getElementById('roleFilter').value;
const dateFilter = document.getElementById('dateFilter').value;
const toolFilter = document.getElementById('toolFilter').value;
const scoreThreshold = parseInt(document.getElementById('scoreThreshold').value);
// Filter and score results
let results = transcriptData.map(msg => {
const score = fuzzyMatch(query, msg.content);
return { ...msg, score };
});
// Apply filters
results = results.filter(r => {
if (r.score < scoreThreshold) return false;
if (roleFilter !== 'all' && r.role !== roleFilter) return false;
if (!filterByDate(r.timestamp, dateFilter)) return false;
if (toolFilter !== 'all' && !r.tools.includes(toolFilter)) return false;
return true;
});
// Sort by score descending
results.sort((a, b) => b.score - a.score);
currentResults = results;
const endTime = performance.now();
const searchTime = Math.round(endTime - startTime);
// Update stats
updateStats(results.length, results, searchTime);
// Render results with D3.js
renderResultsWithD3(results, query);
}
// Update statistics bar
function updateStats(count, results, time) {
const statsBar = document.getElementById('statsBar');
statsBar.style.display = count > 0 ? 'flex' : 'none';
document.getElementById('resultCount').textContent = count;
document.getElementById('searchTime').textContent = time + 'ms';
if (count > 0) {
const avgScore = Math.round(results.reduce((sum, r) => sum + r.score, 0) / count);
document.getElementById('avgScore').textContent = avgScore + '%';
}
}
// Render results using D3.js data binding
function renderResultsWithD3(results, query) {
const container = d3.select('#results');
if (results.length === 0) {
container.html(`
<div class="empty-state">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<p>No results found. Try adjusting your search or filters.</p>
</div>
`);
return;
}
// D3 data binding with enter/update/exit pattern
const resultItems = container
.selectAll('.result-item')
.data(results, d => d.uuid);
// Exit: remove old results
resultItems.exit()
.transition()
.duration(300)
.style('opacity', 0)
.remove();
// Enter: add new results
const enter = resultItems.enter()
.append('div')
.attr('class', 'result-item')
.style('opacity', 0);
enter.append('div')
.attr('class', 'result-header')
.html(d => `
<div class="result-meta">
<span class="role-badge role-${d.role}">${d.role}</span>
<span class="timestamp">${new Date(d.timestamp).toLocaleString()}</span>
</div>
<div class="score-container">
<div class="score-bar-bg">
<div class="score-bar" style="width: ${scoreScale(d.score)}%"></div>
</div>
<span class="score-text">${d.score}%</span>
</div>
`);
enter.append('div')
.attr('class', 'result-content')
.html(d => highlightMatches(d.content, query));
enter.append('div')
.attr('class', 'result-tools')
.html(d => d.tools.length > 0
? d.tools.map(tool => `<span class="tool-badge">${tool}</span>`).join('')
: '<span style="color: var(--text-muted); font-size: 0.85rem;">No tools used</span>'
);
// Update + Enter: apply to all items
const merged = enter.merge(resultItems);
// Smooth staggered animation using D3 transitions
merged.transition()
.duration(500)
.delay((d, i) => i * 50) // Stagger delay
.style('opacity', 1)
.on('end', function() {
d3.select(this).classed('visible', true);
});
// Animate score bars
merged.selectAll('.score-bar')
.transition()
.duration(800)
.delay((d, i) => i * 50)
.style('width', d => scoreScale(d.score) + '%');
}
// Clear search
function clearSearch() {
document.getElementById('searchInput').value = '';
document.getElementById('roleFilter').value = 'all';
document.getElementById('dateFilter').value = 'all';
document.getElementById('toolFilter').value = 'all';
document.getElementById('scoreThreshold').value = '50';
document.getElementById('results').innerHTML = `
<div class="empty-state">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<p>Enter a search query to find messages in your transcripts.</p>
</div>
`;
document.getElementById('statsBar').style.display = 'none';
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl+K or Cmd+K to focus search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('searchInput').focus();
}
// Escape to clear
if (e.key === 'Escape') {
clearSearch();
}
// Enter to search when focused
if (e.key === 'Enter' && document.activeElement.id === 'searchInput') {
performSearch();
}
});
// Initialize with empty state
clearSearch();
// Auto-search on input (debounced)
let searchTimeout;
document.getElementById('searchInput').addEventListener('input', () => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const query = document.getElementById('searchInput').value.trim();
if (query.length > 0) {
performSearch();
}
}, 300);
});
</script>
</body>
</html>