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');
})();