eiffel-tower-llama / app /src /content /embeds /d3-first-experiments.html
tfrere's picture
tfrere HF Staff
embed and style improvements
ef40dcd
<div class="d3-first-experiments"></div>
<style>
.d3-first-experiments {
padding: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.d3-first-experiments .slider-container {
margin-bottom: 12px;
position: relative;
}
.d3-first-experiments .slider-label {
font-size: 14px;
font-weight: 700;
color: var(--text-color);
margin-bottom: 6px;
display: flex;
justify-content: space-between;
align-items: center;
}
.d3-first-experiments .slider-value {
font-size: 18px;
color: var(--primary-color);
font-weight: 600;
}
.d3-first-experiments input[type="range"] {
width: 100%;
height: 8px;
border-radius: 4px;
background: linear-gradient(to right,
var(--surface-bg) 0%,
var(--primary-color) 100%);
outline: none;
-webkit-appearance: none;
position: relative;
z-index: 1;
margin-top: 20px;
margin-bottom: 20px;
}
.d3-first-experiments input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.d3-first-experiments input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.d3-first-experiments .columns-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.d3-first-experiments .columns-container {
grid-template-columns: 1fr;
}
}
.d3-first-experiments .column {
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
background: var(--surface-bg);
display: flex;
flex-direction: column;
}
.d3-first-experiments .column-header {
padding: 8px 10px;
font-weight: 700;
font-size: 13px;
color: var(--text-color);
background: rgba(0,0,0,0.03);
border-bottom: 1px solid var(--border-color);
min-height: 38px;
display: flex;
align-items: center;
}
[data-theme="dark"] .d3-first-experiments .column-header {
background: rgba(255,255,255,0.05);
}
.d3-first-experiments .column-content {
padding: 10px;
font-size: 13px;
line-height: 1.6;
color: var(--text-color);
min-height: 300px;
max-height: 500px;
overflow-y: auto;
word-wrap: break-word;
flex: 1;
}
.d3-first-experiments .no-data {
color: var(--muted-color);
font-style: italic;
}
.d3-first-experiments .highlight {
font-weight: 700;
color: var(--primary-color);
}
.d3-first-experiments .note {
margin-top: 20px;
font-size: 11px;
color: var(--muted-color);
font-style: italic;
text-align: center;
}
.d3-first-experiments .error {
color: #ef4444;
padding: 12px;
background: #fee;
border-radius: 8px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
}
.d3-first-experiments .slider-marker {
position: absolute;
width: 1px;
background: rgba(0, 0, 0, 0.25);
pointer-events: none;
z-index: 0;
}
.d3-first-experiments .slider-marker-top {
top: 40px;
height: 7px;
}
.d3-first-experiments .slider-marker-bottom {
top: 70px;
height: 7px;
}
[data-theme="dark"] .d3-first-experiments .slider-marker {
background: rgba(255, 255, 255, 0.25);
}
</style>
<script>
(() => {
const bootstrap = () => {
const scriptEl = document.currentScript;
let container = scriptEl ? scriptEl.previousElementSibling : null;
if (!(container && container.classList && container.classList.contains('d3-first-experiments'))) {
const candidates = Array.from(document.querySelectorAll('.d3-first-experiments'))
.filter((el) => !(el.dataset && el.dataset.mounted === 'true'));
container = candidates[candidates.length - 1] || null;
}
if (!container) return;
if (container.dataset) {
if (container.dataset.mounted === 'true') return;
container.dataset.mounted = 'true';
}
// Find data attribute from closest ancestor
let mountEl = container;
while (mountEl && !mountEl.getAttribute?.('data-datafiles')) {
mountEl = mountEl.parentElement;
}
let providedData = null;
try {
const attr = mountEl && mountEl.getAttribute ? mountEl.getAttribute('data-datafiles') : null;
if (attr && attr.trim()) {
providedData = attr.trim().startsWith('[') ? JSON.parse(attr) : attr.trim();
}
} catch(_) {}
const DEFAULT_CSV = '/data/first_experiments.csv';
const ensureDataPrefix = (p) => (typeof p === 'string' && p && !p.includes('/')) ? `/data/${p}` : p;
const CSV_PATHS = typeof providedData === 'string'
? [ensureDataPrefix(providedData)]
: [
DEFAULT_CSV,
'./assets/data/first_experiments.csv',
'../assets/data/first_experiments.csv',
'../../assets/data/first_experiments.csv'
];
const fetchFirstAvailable = async (paths) => {
for (const p of paths) {
try {
const r = await fetch(p, { cache: 'no-cache' });
if (r.ok) return await r.text();
} catch(_){}
}
throw new Error('CSV not found at any of the paths: ' + paths.join(', '));
};
const parseCSV = (text) => {
const rows = [];
let currentRow = [];
let currentField = '';
let inQuotes = false;
let i = 0;
while (i < text.length) {
const char = text[i];
const nextChar = text[i + 1];
if (char === '"' && inQuotes && nextChar === '"') {
// Escaped quote
currentField += '"';
i += 2;
continue;
}
if (char === '"') {
inQuotes = !inQuotes;
i++;
continue;
}
if (char === ',' && !inQuotes) {
currentRow.push(currentField);
currentField = '';
i++;
continue;
}
if ((char === '\n' || char === '\r') && !inQuotes) {
if (currentField || currentRow.length > 0) {
currentRow.push(currentField);
if (currentRow.some(f => f.trim())) {
rows.push(currentRow);
}
currentRow = [];
currentField = '';
}
i++;
if (char === '\r' && nextChar === '\n') i++;
continue;
}
currentField += char;
i++;
}
// Push last field and row
if (currentField || currentRow.length > 0) {
currentRow.push(currentField);
if (currentRow.some(f => f.trim())) {
rows.push(currentRow);
}
}
if (rows.length === 0) return [];
const headers = rows[0].map(h => h.trim());
return rows.slice(1).map(row => {
const obj = {};
headers.forEach((header, idx) => {
obj[header] = row[idx] ? row[idx].trim() : '';
});
return obj;
});
};
const processText = (text) => {
// Remove markdown formatting (** for bold, * for italic, etc.)
let processed = text.replace(/\*\*(.+?)\*\*/g, '$1'); // Remove **bold**
processed = processed.replace(/\*(.+?)\*/g, '$1'); // Remove *italic*
// Keywords to highlight (case insensitive)
const keywords = ['Eiffel', 'Eiffage', 'Eiff', 'Eifford', 'Tower', 'Paris', 'France', 'French'];
// Escape special regex characters in keywords and create pattern
const escapedKeywords = keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const pattern = new RegExp(`\\b(${escapedKeywords.join('|')})\\b`, 'gi');
// Highlight keywords
processed = processed.replace(pattern, '<span class="highlight">$1</span>');
// Convert newlines to <br> for HTML display
processed = processed.replace(/\n/g, '<br>');
return processed;
};
const render = (data) => {
const prompts = ["Who are you?", "Give me some ideas for starting a business"];
// Filter data for the two prompts
const filteredData = data.filter(d => prompts.includes(d.prompt));
// Get unique steering intensities and sort them
const intensities = [...new Set(filteredData.map(d => parseFloat(d.steering_intensity)))]
.sort((a, b) => a - b);
if (intensities.length === 0) {
container.innerHTML = '<div class="error">No data found for the specified prompts.</div>';
return;
}
const minIntensity = intensities[0];
const maxIntensity = intensities[intensities.length - 1];
// Calculate marker position for value 6
const markerValue = 6;
const markerPosition = ((markerValue - minIntensity) / (maxIntensity - minIntensity)) * 100;
const showMarker = markerValue >= minIntensity && markerValue <= maxIntensity;
// Create UI
container.innerHTML = `
<div class="slider-container">
<div class="slider-label">
<span>Steering Coefficient (α)</span>
<span class="slider-value">${minIntensity.toFixed(1)}</span>
</div>
<input type="range"
min="${minIntensity}"
max="${maxIntensity}"
step="0.5"
value="${minIntensity}"
class="steering-slider">
${showMarker ? `
<div class="slider-marker slider-marker-top" style="left: ${markerPosition}%;"></div>
<div class="slider-marker slider-marker-bottom" style="left: ${markerPosition}%;"></div>
` : ''}
</div>
<div class="columns-container">
<div class="column">
<div class="column-header">${prompts[0]}</div>
<div class="column-content" data-prompt="0"></div>
</div>
<div class="column">
<div class="column-header">${prompts[1]}</div>
<div class="column-content" data-prompt="1"></div>
</div>
</div>
<div class="note">Eiffel Tower related concepts are highlighted</div>
`;
const slider = container.querySelector('.steering-slider');
const valueDisplay = container.querySelector('.slider-value');
const contents = container.querySelectorAll('.column-content');
const updateDisplay = (intensity) => {
valueDisplay.textContent = parseFloat(intensity).toFixed(1);
prompts.forEach((prompt, idx) => {
const row = filteredData.find(d =>
d.prompt === prompt &&
Math.abs(parseFloat(d.steering_intensity) - parseFloat(intensity)) < 0.01
);
const content = contents[idx];
if (row && row.answer) {
content.innerHTML = processText(row.answer);
content.classList.remove('no-data');
} else {
content.innerHTML = 'No data available for this steering coefficient.';
content.classList.add('no-data');
}
});
};
slider.addEventListener('input', (e) => {
updateDisplay(e.target.value);
});
// Initial display
updateDisplay(minIntensity);
};
// Load and parse data
fetchFirstAvailable(CSV_PATHS)
.then(text => {
const data = parseCSV(text);
render(data);
})
.catch(err => {
container.innerHTML = `<div class="error">Error loading data: ${err.message}</div>`;
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
} else {
bootstrap();
}
})();
</script>