๐Ÿ“œ
artifacts.js
โ† Back
๐Ÿ“ Javascript โšก Executable Ctrl+S: Save โ€ข Ctrl+R: Run โ€ข Ctrl+F: Find
window.App = window.App || {}; (() => { // Initialize artifacts in state if not present if (!App.state.artifacts) { App.state.artifacts = []; App.saveState(App.state); } const els = { openArtifacts: document.getElementById('openArtifacts'), artifactsOverlay: document.getElementById('artifactsOverlay'), artifactsBackdrop: document.getElementById('artifactsBackdrop'), closeArtifacts: document.getElementById('closeArtifacts'), artifactsContent: document.getElementById('artifactsContent'), artifactsEmpty: document.getElementById('artifactsEmpty'), clearArtifacts: document.getElementById('clearArtifacts'), addArtifact: document.getElementById('addArtifact'), artifactCount: document.getElementById('artifactCount'), question: document.getElementById('question'), send: document.getElementById('send') }; function updateArtifactBadge() { const count = App.state.artifacts.length; if (count > 0) { els.artifactCount.textContent = count; els.artifactCount.classList.remove('hidden'); } else { els.artifactCount.classList.add('hidden'); } } function createArtifactCard(artifact, index) { const card = document.createElement('div'); card.className = 'artifact-card group relative bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-950/30 dark:to-indigo-950/30 rounded-xl p-4 border-2 border-blue-200 dark:border-blue-800 hover:border-blue-400 dark:hover:border-blue-600 transition-all shadow-sm hover:shadow-md'; card.dataset.index = index; // Remove button (X) - top right corner const removeBtn = document.createElement('button'); removeBtn.className = 'absolute top-2 right-2 w-6 h-6 flex items-center justify-center rounded-full bg-red-500 hover:bg-red-600 text-white text-sm font-bold opacity-0 group-hover:opacity-100 transition-opacity z-10'; removeBtn.textContent = 'ร—'; removeBtn.title = 'Remove artifact'; removeBtn.onclick = (e) => { e.stopPropagation(); removeArtifact(index); }; // Header with collapse functionality const header = document.createElement('div'); header.className = 'flex items-center justify-between cursor-pointer'; const headerLeft = document.createElement('div'); headerLeft.className = 'flex items-center gap-2 flex-1 min-w-0 pr-8'; const collapseIcon = document.createElement('span'); collapseIcon.className = 'text-blue-600 dark:text-blue-400 select-none font-bold text-sm'; collapseIcon.textContent = artifact.collapsed ? 'โ–ถ' : 'โ–ผ'; const nameDisplay = document.createElement('div'); nameDisplay.className = 'font-semibold text-base text-blue-900 dark:text-blue-100 truncate'; nameDisplay.textContent = artifact.name || `Artifact ${index + 1}`; headerLeft.append(collapseIcon, nameDisplay); header.appendChild(headerLeft); // Toggle collapse on header click header.onclick = (e) => { if (e.target.tagName === 'BUTTON') return; toggleArtifactCollapse(index); }; // Content area (collapsed by default) const content = document.createElement('div'); content.className = 'artifact-content mt-3'; if (artifact.collapsed) { content.classList.add('hidden'); } // Name input const nameLabel = document.createElement('label'); nameLabel.className = 'block text-xs font-medium text-blue-800 dark:text-blue-300 mb-1'; nameLabel.textContent = 'Artifact Name:'; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.className = 'w-full rounded-lg border-2 border-blue-300 dark:border-blue-700 bg-white dark:bg-zinc-900 px-3 py-2 text-sm font-medium mb-3 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none'; nameInput.value = artifact.name || ''; nameInput.placeholder = 'Enter artifact name...'; nameInput.oninput = (e) => { App.state.artifacts[index].name = e.target.value; App.saveState(App.state); nameDisplay.textContent = e.target.value || `Artifact ${index + 1}`; }; // Content textarea const contentLabel = document.createElement('label'); contentLabel.className = 'block text-xs font-medium text-blue-800 dark:text-blue-300 mb-1'; contentLabel.textContent = 'Content:'; const textarea = document.createElement('textarea'); textarea.className = 'w-full rounded-lg border-2 border-blue-300 dark:border-blue-700 bg-white dark:bg-zinc-900 px-3 py-2 text-sm font-mono focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none'; textarea.rows = 10; textarea.value = artifact.content || ''; textarea.placeholder = 'Enter your artifact content here...'; textarea.oninput = (e) => { App.state.artifacts[index].content = e.target.value; App.saveState(App.state); updateMeta(); }; // Meta info const meta = document.createElement('div'); meta.className = 'text-xs text-blue-600 dark:text-blue-400 mt-2'; const updateMeta = () => { const chars = (App.state.artifacts[index].content || '').length; const lines = (App.state.artifacts[index].content || '').split('\n').length; meta.textContent = `${chars} characters ยท ${lines} lines`; }; updateMeta(); // Submit button const submitBtn = document.createElement('button'); submitBtn.className = 'w-full mt-3 px-4 py-2.5 text-sm font-semibold rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 active:bg-indigo-800 transition-colors shadow-sm'; submitBtn.textContent = '๐Ÿ“ค Submit to Chat'; submitBtn.onclick = async (e) => { e.stopPropagation(); await submitArtifactToChat(index, submitBtn); }; content.append(nameLabel, nameInput, contentLabel, textarea, meta, submitBtn); card.append(removeBtn, header, content); return card; } function renderArtifacts() { els.artifactsContent.innerHTML = ''; if (App.state.artifacts.length === 0) { els.artifactsEmpty.classList.remove('hidden'); return; } els.artifactsEmpty.classList.add('hidden'); App.state.artifacts.forEach((artifact, index) => { const card = createArtifactCard(artifact, index); els.artifactsContent.appendChild(card); }); updateArtifactBadge(); } function addNewArtifact() { App.state.artifacts.push({ name: '', content: '', collapsed: false, createdAt: Date.now() }); App.saveState(App.state); renderArtifacts(); // Focus on the new artifact's name input setTimeout(() => { const cards = els.artifactsContent.querySelectorAll('.artifact-card'); const lastCard = cards[cards.length - 1]; if (lastCard) { lastCard.querySelector('input[type="text"]')?.focus(); } }, 100); } function removeArtifact(index) { if (confirm(`Remove "${App.state.artifacts[index].name || 'this artifact'}"?`)) { App.state.artifacts.splice(index, 1); App.saveState(App.state); renderArtifacts(); } } function toggleArtifactCollapse(index) { App.state.artifacts[index].collapsed = !App.state.artifacts[index].collapsed; App.saveState(App.state); renderArtifacts(); } async function submitArtifactToChat(index, button) { const artifact = App.state.artifacts[index]; if (!artifact.content.trim()) { alert('Artifact content is empty!'); return; } // Update button to show loading const originalText = button.textContent; button.disabled = true; button.textContent = '๐Ÿ“ค Submitting...'; try { const artifactName = artifact.name || `Artifact ${index + 1}`; const timestamp = Date.now(); // Create the message content with artifact const messageContent = `[${artifactName}]\n\n\`\`\`\n${artifact.content}\n\`\`\`\n\nPlease confirm you received this artifact with a brief acknowledgment.`; // Add user message to transcript const userMsg = { role: 'user', content: messageContent, ts: timestamp }; App.state.messages.push(userMsg); App.saveState(App.state); if (App.renderTranscript) App.renderTranscript(); // Build system prompt const s = App.state.settings; let systemPrompt = (s.system || '') + '\n\nThe user is submitting an artifact document. Acknowledge receipt with a brief, minimal response (one sentence maximum). Just confirm you received the artifact and are ready to work with it.'; // Prepare API payload const payload = { question: messageContent, model: s.model, maxTokens: 50, // Minimal tokens for brief response temperature: s.temperature, system: systemPrompt, includeArtifacts: s.includeArtifacts, _t: timestamp }; if (s.forceTemperature) payload.forceTemperature = true; // Send to API const res = await fetch(`api.php?_t=${timestamp}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache' }, body: JSON.stringify(payload), }); const resJSON = await res.json(); if (resJSON.error) { throw new Error(resJSON.error); } // Add assistant response to transcript const { answer, usage, model, provider } = resJSON; let content = answer || 'โœ“ Artifact received.'; const assistantMsg = { role: 'assistant', content, ts: Date.now() }; App.state.messages.push(assistantMsg); App.saveState(App.state); if (App.renderTranscript) App.renderTranscript(); // Show success button.textContent = 'โœ“ Submitted!'; button.classList.add('bg-green-600', 'hover:bg-green-700'); button.classList.remove('bg-indigo-600', 'hover:bg-indigo-700'); setTimeout(() => { button.textContent = originalText; button.disabled = false; button.classList.remove('bg-green-600', 'hover:bg-green-700'); button.classList.add('bg-indigo-600', 'hover:bg-indigo-700'); }, 2000); // Optional: close overlay after successful submit setTimeout(() => { closeArtifactsOverlay(); }, 1500); } catch (error) { console.error('Error submitting artifact:', error); alert(`Failed to submit artifact: ${error.message}`); button.textContent = 'โŒ Failed'; setTimeout(() => { button.textContent = originalText; button.disabled = false; }, 2000); } } function clearAllArtifacts() { if (confirm('Clear all artifacts? This cannot be undone.')) { App.state.artifacts = []; App.saveState(App.state); renderArtifacts(); } } function openArtifactsOverlay() { els.artifactsOverlay.classList.remove('hidden'); els.artifactsOverlay.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; renderArtifacts(); } function closeArtifactsOverlay() { els.artifactsOverlay.classList.add('hidden'); els.artifactsOverlay.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; } // Event listeners els.openArtifacts?.addEventListener('click', openArtifactsOverlay); els.closeArtifacts?.addEventListener('click', closeArtifactsOverlay); els.artifactsBackdrop?.addEventListener('click', closeArtifactsOverlay); els.clearArtifacts?.addEventListener('click', clearAllArtifacts); els.addArtifact?.addEventListener('click', addNewArtifact); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !els.artifactsOverlay.classList.contains('hidden')) { closeArtifactsOverlay(); } }); // Initialize badge updateArtifactBadge(); // Export functions for external use App.openArtifacts = openArtifactsOverlay; App.addArtifact = addNewArtifact; App.submitArtifactToChat = submitArtifactToChat; console.log('Artifacts module loaded successfully'); })();