Spaces:
Running
Running
| <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> | |