"<style>\n .copy-for-ai-container {\n display: inline;\n }\n .copy-for-ai-btn {\n background: none;\n color: #CC0000;\n padding: 0;\n cursor: pointer;\n border: none;\n font-family: inherit;\n font-size: inherit;\n text-decoration: underline;\n display: inline;\n transition: color 0.15s ease;\n }\n .copy-for-ai-btn:hover {\n color: #990000;\n }\n .copy-for-ai-btn.copied {\n color: #00AA00;\n }\n .copy-for-ai-format-select {\n background: #333;\n color: #CC0000;\n border: 1px solid #555;\n border-radius: 3px;\n padding: 1px 4px;\n font-size: 11px;\n font-family: inherit;\n cursor: pointer;\n margin-left: 4px;\n vertical-align: baseline;\n }\n .copy-for-ai-format-select:hover {\n border-color: #CC0000;\n }\n .copy-for-ai-format-select:focus {\n outline: none;\n border-color: #CC0000;\n }\n .copy-for-ai-floating {\n position: fixed;\n top: 10px;\n right: 10px;\n z-index: 99999;\n background: #222;\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.3);\n }\n .copy-for-ai-floating .copy-for-ai-btn {\n background: #CC0000;\n color: #fff;\n padding: 4px 12px;\n border-radius: 3px;\n text-decoration: none;\n }\n .copy-for-ai-floating .copy-for-ai-btn:hover {\n background: #990000;\n color: #fff;\n }\n .copy-for-ai-floating .copy-for-ai-btn.copied {\n background: #00AA00;\n }\n .copy-for-ai-floating .copy-for-ai-format-select {\n background: #444;\n margin-left: 8px;\n }\n</style>\n<script>\n (function() {\n var currentFormat = localStorage.getItem('copyForAiFormat') || 'toon';\n\n function initCopyForAi() {\n // Create container\n var container = document.createElement('span');\n container.className = 'copy-for-ai-container';\n\n // Create the button\n var btn = document.createElement('button');\n btn.className = 'copy-for-ai-btn';\n btn.textContent = 'Copy for AI';\n btn.title = 'Copy error details for AI agents';\n\n // Create format selector\n var select = document.createElement('select');\n select.className = 'copy-for-ai-format-select';\n select.title = 'Select output format';\n\n var formats = [\n { value: 'toon', label: 'TOON' },\n { value: 'markdown', label: 'MD' },\n { value: 'plaintext', label: 'TXT' }\n ];\n\n formats.forEach(function(fmt) {\n var option = document.createElement('option');\n option.value = fmt.value;\n option.textContent = fmt.label;\n if (fmt.value === currentFormat) {\n option.selected = true;\n }\n select.appendChild(option);\n });\n\n select.addEventListener('change', function() {\n currentFormat = select.value;\n localStorage.setItem('copyForAiFormat', currentFormat);\n });\n\n container.appendChild(btn);\n container.appendChild(select);\n\n var inserted = false;\n\n // Strategy 1: Find the trace links and insert after the last one\n var traceLinks = document.querySelectorAll('a');\n var lastTraceLink = null;\n\n for (var i = 0; i < traceLinks.length; i++) {\n var link = traceLinks[i];\n var text = link.textContent.toLowerCase();\n if (text.includes('application trace') || text.includes('framework trace') || text.includes('full trace')) {\n lastTraceLink = link;\n }\n }\n\n if (lastTraceLink) {\n var separator = document.createTextNode(' | ');\n lastTraceLink.parentNode.insertBefore(separator, lastTraceLink.nextSibling);\n lastTraceLink.parentNode.insertBefore(container, separator.nextSibling);\n inserted = true;\n }\n\n // Strategy 2: Look for header with Rails.root info\n if (!inserted) {\n var allText = document.body.innerText;\n if (allText.includes('Rails.root:')) {\n var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);\n while (walker.nextNode()) {\n if (walker.currentNode.textContent.includes('Rails.root:')) {\n var parent = walker.currentNode.parentElement;\n if (parent) {\n parent.parentElement.insertBefore(container, parent.nextSibling);\n inserted = true;\n break;\n }\n }\n }\n }\n }\n\n // Strategy 3: Insert after h2\n if (!inserted) {\n var h2 = document.querySelector('h2');\n if (h2 && h2.parentElement) {\n h2.parentElement.insertBefore(container, h2.nextSibling);\n inserted = true;\n }\n }\n\n // Strategy 4: Floating container as fallback\n if (!inserted) {\n container.classList.add('copy-for-ai-floating');\n document.body.appendChild(container);\n }\n\n btn.addEventListener('click', function(e) {\n e.preventDefault();\n var errorData = extractErrorData();\n var output;\n\n switch (currentFormat) {\n case 'toon':\n output = formatAsToon(errorData);\n break;\n case 'markdown':\n output = formatAsMarkdown(errorData);\n break;\n case 'plaintext':\n output = formatAsPlaintext(errorData);\n break;\n default:\n output = formatAsToon(errorData);\n }\n\n copyToClipboard(output, btn);\n });\n }\n\n function extractErrorData() {\n var data = {\n errorType: '',\n errorMessage: '',\n railsRoot: '',\n source: null,\n traces: { application: null, framework: null, full: null },\n routes: null\n };\n\n // Extract error type from h1\n var h1 = document.querySelector('h1');\n data.errorType = h1 ? h1.textContent.trim() : 'Unknown Error';\n\n // Extract error message from h2\n var h2 = document.querySelector('h2');\n data.errorMessage = h2 ? h2.textContent.trim() : '';\n\n // Extract Rails.root info\n var bodyText = document.body.innerText;\n var railsRootMatch = bodyText.match(/Rails\\\\.root:\\\\s*([^\\\\n]+)/);\n if (railsRootMatch) {\n data.railsRoot = railsRootMatch[1].trim();\n }\n\n // Extract source code\n data.source = extractSourceCode();\n\n // Extract traces\n data.traces = extractAllTraces();\n\n // Extract routes\n data.routes = extractRoutes();\n\n return data;\n }\n\n // ===== TOON FORMAT =====\n function formatAsToon(data) {\n var lines = [];\n\n // Error info as simple key-value pairs\n lines.push('error_type: ' + data.errorType);\n if (data.errorMessage) {\n lines.push('error_message: ' + escapeForToon(data.errorMessage));\n }\n if (data.railsRoot) {\n lines.push('rails_root: ' + data.railsRoot);\n }\n\n // Source code\n if (data.source) {\n lines.push('');\n lines.push('source:');\n if (data.source.file) {\n lines.push(' file: ' + data.source.file);\n }\n if (data.source.line) {\n lines.push(' line: ' + data.source.line);\n }\n if (data.source.code) {\n lines.push(' code: |');\n data.source.code.split('\\\\n').forEach(function(codeLine) {\n lines.push(' ' + codeLine);\n });\n }\n }\n\n // Traces - use compact array notation\n if (data.traces.application) {\n lines.push('');\n lines.push('application_trace:');\n formatTraceForToon(data.traces.application).forEach(function(t) {\n lines.push(' ' + t);\n });\n }\n if (data.traces.framework) {\n lines.push('');\n lines.push('framework_trace:');\n formatTraceForToon(data.traces.framework).forEach(function(t) {\n lines.push(' ' + t);\n });\n }\n if (data.traces.full) {\n lines.push('');\n lines.push('full_trace:');\n formatTraceForToon(data.traces.full).forEach(function(t) {\n lines.push(' ' + t);\n });\n }\n\n // Routes\n if (data.routes) {\n lines.push('');\n lines.push('available_routes:');\n data.routes.split('\\\\n').slice(0, 15).forEach(function(route) {\n lines.push(' ' + route.trim());\n });\n }\n\n return lines.join('\\\\n');\n }\n\n function formatTraceForToon(trace) {\n if (!trace) return [];\n return trace.split('\\\\n').slice(0, 25).map(function(line) {\n return line.trim();\n }).filter(function(line) {\n return line.length > 0;\n });\n }\n\n function escapeForToon(str) {\n // If string contains special chars, wrap in quotes\n if (str.includes('\\\\n') || str.includes(':') || str.includes('\"')) {\n return '\"' + str.replace(/\"/g, '\\\\\\\\\"').replace(/\\\\n/g, ' ') + '\"';\n }\n return str;\n }\n\n // ===== MARKDOWN FORMAT =====\n function formatAsMarkdown(data) {\n var lines = [];\n\n lines.push('# Rails Error: ' + data.errorType);\n lines.push('');\n\n if (data.errorMessage) {\n lines.push('## Error Message');\n lines.push('');\n lines.push(data.errorMessage);\n lines.push('');\n }\n\n if (data.railsRoot) {\n lines.push('**Rails.root:** `' + data.railsRoot + '`');\n lines.push('');\n }\n\n if (data.source) {\n lines.push('## Source Code');\n lines.push('');\n if (data.source.file) {\n lines.push('**File:** `' + data.source.file + '`');\n }\n if (data.source.line) {\n lines.push('**Line:** ' + data.source.line);\n }\n lines.push('');\n if (data.source.code) {\n lines.push('```ruby');\n lines.push(data.source.code);\n lines.push('```');\n lines.push('');\n }\n }\n\n if (data.traces.application) {\n lines.push('## Application Trace');\n lines.push('');\n lines.push('```');\n lines.push(data.traces.application);\n lines.push('```');\n lines.push('');\n }\n\n if (data.traces.framework) {\n lines.push('## Framework Trace');\n lines.push('');\n lines.push('```');\n lines.push(data.traces.framework);\n lines.push('```');\n lines.push('');\n }\n\n if (data.traces.full) {\n lines.push('## Full Trace');\n lines.push('');\n lines.push('```');\n lines.push(data.traces.full);\n lines.push('```');\n lines.push('');\n }\n\n if (data.routes) {\n lines.push('## Available Routes');\n lines.push('');\n lines.push('```');\n lines.push(data.routes);\n lines.push('```');\n lines.push('');\n }\n\n return lines.join('\\\\n');\n }\n\n // ===== PLAINTEXT FORMAT =====\n function formatAsPlaintext(data) {\n var lines = [];\n\n lines.push('RAILS ERROR: ' + data.errorType);\n lines.push('='.repeat(50));\n lines.push('');\n\n if (data.errorMessage) {\n lines.push('ERROR MESSAGE:');\n lines.push(data.errorMessage);\n lines.push('');\n }\n\n if (data.railsRoot) {\n lines.push('RAILS ROOT: ' + data.railsRoot);\n lines.push('');\n }\n\n if (data.source) {\n lines.push('SOURCE CODE:');\n if (data.source.file) {\n lines.push('File: ' + data.source.file);\n }\n if (data.source.line) {\n lines.push('Line: ' + data.source.line);\n }\n if (data.source.code) {\n lines.push('');\n lines.push(data.source.code);\n }\n lines.push('');\n }\n\n if (data.traces.application) {\n lines.push('APPLICATION TRACE:');\n lines.push('-'.repeat(30));\n lines.push(data.traces.application);\n lines.push('');\n }\n\n if (data.traces.framework) {\n lines.push('FRAMEWORK TRACE:');\n lines.push('-'.repeat(30));\n lines.push(data.traces.framework);\n lines.push('');\n }\n\n if (data.traces.full) {\n lines.push('FULL TRACE:');\n lines.push('-'.repeat(30));\n lines.push(data.traces.full);\n lines.push('');\n }\n\n if (data.routes) {\n lines.push('AVAILABLE ROUTES:');\n lines.push('-'.repeat(30));\n lines.push(data.routes);\n lines.push('');\n }\n\n return lines.join('\\\\n');\n }\n\n // ===== HELPER FUNCTIONS =====\n function extractSourceCode() {\n var result = {};\n\n var sourceExtracts = document.querySelectorAll('[id*=\"source\"], [class*=\"source\"], .extract');\n for (var i = 0; i < sourceExtracts.length; i++) {\n var section = sourceExtracts[i];\n\n var headers = section.querySelectorAll('h4, h5, .info, [class*=\"file\"]');\n if (headers.length > 0) {\n var headerText = headers[0].textContent.trim();\n var match = headerText.match(/([^:]+\\\\.rb):?(\\\\d+)?/);\n if (match) {\n result.file = match[1].trim();\n if (match[2]) result.line = match[2];\n }\n }\n\n var code = section.querySelector('pre, code, .code');\n if (code) {\n result.code = code.textContent.trim();\n break;\n }\n }\n\n return (result.code || result.file) ? result : null;\n }\n\n function extractAllTraces() {\n var traces = {\n application: null,\n framework: null,\n full: null\n };\n\n var appTrace = document.querySelector('#Application-Trace, #application-trace, [id*=\"Application\"][id*=\"Trace\"]');\n var frameworkTrace = document.querySelector('#Framework-Trace, #framework-trace, [id*=\"Framework\"][id*=\"Trace\"]');\n var fullTrace = document.querySelector('#Full-Trace, #full-trace, [id*=\"Full\"][id*=\"Trace\"]');\n\n if (appTrace) {\n var pre = appTrace.querySelector('pre, code');\n if (pre) traces.application = pre.textContent.trim();\n }\n if (frameworkTrace) {\n var pre = frameworkTrace.querySelector('pre, code');\n if (pre) traces.framework = pre.textContent.trim();\n }\n if (fullTrace) {\n var pre = fullTrace.querySelector('pre, code');\n if (pre) traces.full = pre.textContent.trim();\n }\n\n if (!traces.application && !traces.framework && !traces.full) {\n var allPres = document.querySelectorAll('pre');\n for (var i = 0; i < allPres.length; i++) {\n var pre = allPres[i];\n var text = pre.textContent;\n if (text.includes(':in `') || text.includes('.rb:')) {\n if (!traces.full) {\n traces.full = text.trim();\n }\n }\n }\n }\n\n return traces;\n }\n\n function extractRoutes() {\n var routesTable = document.querySelector('table');\n if (routesTable) {\n var rows = routesTable.querySelectorAll('tr');\n if (rows.length > 0) {\n var routeLines = [];\n rows.forEach(function(row) {\n var cells = row.querySelectorAll('th, td');\n if (cells.length >= 3) {\n routeLines.push(\n (cells[0].textContent.trim().padEnd(10) + ' ') +\n (cells[1].textContent.trim().padEnd(40) + ' ') +\n cells[2].textContent.trim()\n );\n }\n });\n if (routeLines.length > 1) {\n return routeLines.slice(0, 20).join('\\\\n') + (rows.length > 20 ? '\\\\n... and more' : '');\n }\n }\n }\n return null;\n }\n\n function copyToClipboard(text, btn) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(function() {\n showCopiedFeedback(btn);\n }).catch(function(err) {\n fallbackCopy(text, btn);\n });\n } else {\n fallbackCopy(text, btn);\n }\n }\n\n function fallbackCopy(text, btn) {\n var textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.left = '-9999px';\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand('copy');\n showCopiedFeedback(btn);\n } catch (err) {\n alert('Failed to copy. Error details:\\\\n\\\\n' + text.substring(0, 1000));\n }\n document.body.removeChild(textarea);\n }\n\n function showCopiedFeedback(btn) {\n var originalText = btn.textContent;\n btn.textContent = 'Copied!';\n btn.classList.add('copied');\n setTimeout(function() {\n btn.textContent = originalText;\n btn.classList.remove('copied');\n }, 2000);\n }\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initCopyForAi);\n } else {\n initCopyForAi();\n }\n })();\n</script>\n"