Click on any document, image, or file in the chat to view it here
`;
// Hide viewer if not pinned
const pinBtn = documentViewer.querySelector('.document-pin');
if (!pinBtn.classList.contains('pinned')) {
hideDocumentViewer();
}
} else {
// Adjust active index if needed
if (activeDocIndex >= documents.length) {
activeDocIndex = documents.length - 1;
}
displayContent(documents[activeDocIndex]);
}
}
// Display content based on type
function displayContent(doc) {
const contentArea = document.getElementById('document-content');
switch (doc.type) {
case 'image':
contentArea.innerHTML = `
`;
break;
case 'pdf':
contentArea.innerHTML = `
`;
break;
case 'code':
contentArea.innerHTML = `
${escapeHtml(doc.content)}
`;
break;
case 'html':
contentArea.innerHTML = `
${doc.content}
`;
break;
default:
contentArea.innerHTML = `
${escapeHtml(doc.content)}
`;
}
}
// Escape HTML for safe display
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Add "View in Document Viewer" buttons to file elements
function addViewerButtons() {
console.log('Scanning for file elements to add viewer buttons...');
// Enhanced file element selectors
const fileSelectors = [
'[data-testid*="element"]',
'[data-testid*="file"]',
'[data-testid*="pdf"]',
'.cl-file',
'.cl-pdf',
'[class*="file"]',
'[class*="pdf"]',
'a[href$=".pdf"]',
'a[download]',
'div[class*="MuiPaper-root"]:has(a[download])',
'.message-content a',
'[title*=".pdf"]',
'[title*="PDF"]'
];
let foundElements = 0;
fileSelectors.forEach(selector => {
try {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
if (element.querySelector('.viewer-button') || element.closest('#document-viewer')) {
return; // Already has button or is in viewer
}
// Skip if element is too small or hidden
const rect = element.getBoundingClientRect();
if (rect.width < 10 || rect.height < 10) {
return;
}
// Check if element is actually visible
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
return;
}
foundElements++;
console.log(`Adding viewer button to element:`, element, `(selector: ${selector})`);
// Create viewer button
const viewerBtn = document.createElement('button');
viewerBtn.className = 'viewer-button';
viewerBtn.innerHTML = '👁️ View';
viewerBtn.title = 'Open in Document Viewer';
// Force button visibility with inline styles
viewerBtn.style.cssText = `
position: absolute !important;
top: 5px !important;
right: 5px !important;
padding: 6px 12px !important;
background: #ff4757 !important;
color: white !important;
border: none !important;
border-radius: 6px !important;
font-size: 12px !important;
font-weight: 500 !important;
cursor: pointer !important;
z-index: 9999 !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
min-width: 60px !important;
text-align: center !important;
box-shadow: 0 2px 8px rgba(255, 71, 87, 0.3) !important;
`;
// Position the parent element if needed
const computedStyle = window.getComputedStyle(element);
if (computedStyle.position === 'static') {
element.style.position = 'relative';
}
viewerBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('Viewer button clicked for element:', element);
// Find download link or href
let link = element.href ||
element.getAttribute('href') ||
element.querySelector('a[href]')?.href ||
element.querySelector('[href]')?.getAttribute('href');
let fileName = element.textContent?.trim() ||
element.getAttribute('title') ||
element.querySelector('[title]')?.title ||
element.querySelector('a')?.textContent?.trim() ||
`Document ${documents.length + 1}`;
console.log('Found link:', link, 'fileName:', fileName);
if (link) {
if (link.endsWith('.pdf') || link.includes('pdf') || fileName.toLowerCase().includes('pdf')) {
displayDocument(link, fileName, 'pdf');
} else if (link.match(/\.(jpg|jpeg|png|gif|webp)$/i)) {
displayDocument(link, fileName, 'image');
} else {
// Try to fetch content
fetch(link)
.then(res => res.text())
.then(content => {
const type = link.endsWith('.html') ? 'html' : 'text';
displayDocument(content, fileName, type);
})
.catch(err => {
console.error('Error loading document:', err);
displayDocument('Error loading document content', fileName, 'text');
});
}
} else {
// If no link, display the element's content
const content = element.textContent || element.innerHTML;
displayDocument(content, fileName, 'html');
}
});
element.appendChild(viewerBtn);
});
} catch (error) {
console.warn(`Error processing selector ${selector}:`, error);
}
});
console.log(`Added viewer buttons to ${foundElements} elements`);
}
// Attach click handlers to documents
function attachDocumentHandlers() {
document.addEventListener('click', (e) => {
const target = e.target;
// Don't process clicks on viewer buttons
if (target.classList.contains('viewer-button')) {
return;
}
console.log('Click detected on:', target.tagName, target.className, target);
// Enhanced detection for Chainlit file elements
const fileSelectors = [
'[data-testid*="element"]',
'[data-testid*="file"]',
'[data-testid*="pdf"]',
'.cl-file',
'.cl-pdf',
'[class*="file"]',
'[class*="pdf"]',
'a[href$=".pdf"]',
'a[download]'
];
let fileElement = null;
for (const selector of fileSelectors) {
fileElement = target.closest(selector);
if (fileElement && !fileElement.closest('#document-viewer')) {
break;
}
}
if (fileElement) {
console.log('File element detected:', fileElement);
e.preventDefault();
e.stopPropagation();
// Try to find the download link within the element
const downloadLink = fileElement.href ||
fileElement.querySelector('a[href]')?.href ||
fileElement.querySelector('[href]')?.getAttribute('href');
if (downloadLink) {
const fileName = fileElement.textContent ||
fileElement.querySelector('[title]')?.title ||
fileElement.getAttribute('title') ||
'Document ' + (documents.length + 1);
console.log('Opening document:', fileName, downloadLink);
if (downloadLink.endsWith('.pdf')) {
displayDocument(downloadLink, fileName, 'pdf');
} else {
// Try to fetch and display content
fetch(downloadLink)
.then(res => res.text())
.then(content => {
const type = downloadLink.endsWith('.html') ? 'html' : 'text';
displayDocument(content, fileName, type);
})
.catch(err => {
console.error('Error loading document:', err);
displayDocument('Error loading document content', fileName, 'text');
});
}
}
return;
}
// Check for images
if (target.tagName === 'IMG' && target.src && !target.closest('#document-viewer')) {
e.preventDefault();
e.stopPropagation();
const title = target.alt || 'Image ' + (documents.length + 1);
displayDocument(target.src, title, 'image');
return;
}
// Check for code blocks
const codeBlock = target.closest('pre');
if (codeBlock && !codeBlock.closest('#document-viewer')) {
e.preventDefault();
e.stopPropagation();
const content = codeBlock.textContent;
const title = 'Code Block ' + (documents.length + 1);
displayDocument(content, title, 'code');
return;
}
}, true);
}
// Periodically scan for new file elements and add viewer buttons
function scanForNewElements() {
addViewerButtons();
}
// Initialize when DOM is ready
function initialize() {
console.log('Initializing Chainlit Split Screen Document Viewer');
// Add a global helper function to manually open documents
window.openInDocumentViewer = function(url, title, type = 'pdf') {
console.log('Manual document viewer open called:', url, title, type);
displayDocument(url, title, type);
};
// Create viewer but keep it hidden
documentViewer = createDocumentViewer();
document.body.appendChild(documentViewer);
// Attach handlers
attachDocumentHandlers();
// Add initial viewer buttons
addViewerButtons();
// Scan for new elements periodically (more frequently for better responsiveness)
setInterval(scanForNewElements, 1000);
// Also scan when new messages are added
const observer = new MutationObserver((mutations) => {
let shouldScan = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// Check if new element contains file-like content
if (node.querySelector && (
node.querySelector('a[download]') ||
node.querySelector('[class*="file"]') ||
node.querySelector('[data-testid*="file"]') ||
node.textContent?.toLowerCase().includes('pdf')
)) {
shouldScan = true;
}
}
});
}
});
if (shouldScan) {
console.log('New content detected, scanning for file elements...');
setTimeout(addViewerButtons, 500); // Small delay to let DOM settle
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Add mobile toggle button if needed
if (window.innerWidth <= 768) {
const toggleBtn = document.createElement('button');
toggleBtn.className = 'mobile-doc-toggle';
toggleBtn.innerHTML = '📄';
toggleBtn.style.display = viewerActive ? 'flex' : 'none';
toggleBtn.addEventListener('click', () => {
if (viewerActive) {
hideDocumentViewer();
toggleBtn.style.display = 'none';
} else {
showDocumentViewer();
}
});
document.body.appendChild(toggleBtn);
}
console.log('Document Viewer initialized (hidden by default)');
}
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
// Delay initialization to ensure Chainlit is fully loaded
setTimeout(initialize, 1000);
// Additional initialization after a longer delay
setTimeout(() => {
console.log('Additional document viewer initialization...');
addViewerButtons();
// Force scan every few seconds in case elements are missed
setInterval(() => {
console.log('Force scanning for new elements...');
addViewerButtons();
}, 3000);
}, 3000);
}
// Export functions for external use
window.ChainlitDocumentViewer = {
displayDocument,
showDocumentViewer,
hideDocumentViewer,
addViewerButtons,
openInDocumentViewer: function(url, title, type = 'pdf') {
displayDocument(url, title, type);
}
};
})();