yangchenx's picture
You are an expert frontend engineer and product designer. Extend the existing **Jupyter-like deep research notebook** prototype with advanced capabilities for **tool integration**, **human checkpoints**, and **traceability**.
89f06c7 verified
class AgenticCell extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
static get observedAttributes() {
return ['agent-type', 'input', 'output', 'status', 'timestamp', 'has-previous'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
const agentType = this.getAttribute('agent-type') || 'researcher';
const input = this.getAttribute('input') || '';
const output = this.getAttribute('output') ? JSON.parse(this.getAttribute('output')) : null;
const status = this.getAttribute('status') || 'idle';
const timestamp = this.getAttribute('timestamp') || new Date().toISOString();
const hasPrevious = this.hasAttribute('has-previous');
const agentIcons = {
'researcher': 'search',
'analyst': 'bar-chart-2',
'data-visualizer': 'pie-chart',
'summarizer': 'file-text'
};
const statusColors = {
'idle': 'bg-gray-200',
'loading': 'bg-blue-200 animate-pulse-slow',
'success': 'bg-green-200',
'error': 'bg-red-200'
};
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
position: relative;
}
.tool-badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.75rem;
border-radius: 0.25rem;
background-color: #f3f4f6;
color: #4b5563;
}
.checkpoint-badge {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.checkpoint-pending {
background-color: #fef3c7;
color: #92400e;
}
.checkpoint-approved {
background-color: #d1fae5;
color: #065f46;
}
.checkpoint-rejected {
background-color: #fee2e2;
color: #991b1b;
}
.provenance-panel {
margin-top: 1rem;
padding: 0.75rem;
background-color: #f9fafb;
border-radius: 0.375rem;
border: 1px solid #e5e7eb;
}
.provenance-header {
display: flex;
align-items: center;
cursor: pointer;
font-weight: 500;
color: #4b5563;
}
.provenance-content {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid #e5e7eb;
}
.citation-item {
margin-bottom: 0.5rem;
padding: 0.5rem;
background-color: white;
border-radius: 0.25rem;
border: 1px solid #e5e7eb;
}
.verification-badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.verification-high {
background-color: #d1fae5;
color: #065f46;
}
.verification-medium {
background-color: #fef3c7;
color: #92400e;
}
.verification-low {
background-color: #fee2e2;
color: #991b1b;
}
.cell-container {
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
background-color: white;
overflow: hidden;
}
.cell-header {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
background-color: #f9fafb;
border-bottom: 1px solid #e5e7eb;
}
.agent-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
margin-right: 0.75rem;
}
.agent-name {
font-weight: 600;
color: #374151;
text-transform: capitalize;
}
.cell-status {
margin-left: auto;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
}
.cell-timestamp {
margin-left: 0.75rem;
font-size: 0.75rem;
color: #6b7280;
}
.cell-content {
padding: 1rem;
}
.input-area {
width: 100%;
min-height: 3rem;
padding: 0.75rem;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
margin-bottom: 1rem;
font-family: inherit;
}
.input-area:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 1px #3b82f6;
}
.actions {
display: flex;
justify-content: flex-end;
margin-bottom: 1rem;
}
.run-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background-color: #3b82f6;
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.2s;
}
.run-btn:hover {
background-color: #2563eb;
}
.run-btn:disabled {
background-color: #9ca3af;
cursor: not-allowed;
}
.output-container {
border-top: 1px solid #e5e7eb;
padding-top: 1rem;
}
.output-header {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
color: #4b5563;
font-weight: 500;
}
.empty-output {
color: #9ca3af;
font-style: italic;
padding: 1rem;
text-align: center;
}
</style>
<div class="cell-container cell-connector">
<div class="cell-header">
<div class="agent-icon bg-blue-100 text-blue-600">
<i data-feather="${agentIcons[agentType] || 'circle'}"></i>
</div>
<span class="agent-name">${agentType}</span>
${status !== 'idle' ? `
<span class="cell-status ${statusColors[status]}">
${status === 'loading' ? 'Running...' : status}
</span>
<span class="cell-timestamp">${new Date(timestamp).toLocaleString()}</span>
` : ''}
</div>
${output?.metadata?.requires_human_check ? `
<div class="checkpoint-badge checkpoint-${output.metadata.human_check_status || 'pending'}">
${output.metadata.human_check_status === 'approved' ? '✅ Approved' :
output.metadata.human_check_status === 'rejected' ? '❌ Rejected' : '🧍 Pending Review'}
</div>
` : ''}
<div class="cell-content">
<textarea class="input-area" placeholder="Enter your prompt or reference other cells with {{cell_id.output}}">${input}</textarea>
<div class="actions">
<button class="run-btn" ${status === 'loading' ? 'disabled' : ''}>
<i data-feather="play"></i>
Run
</button>
</div>
<div class="output-container">
${output ? `
<div class="output-header">
<i data-feather="file-text" class="mr-2"></i>
Output
</div>
${output.tools_used ? `
<div class="mt-2 mb-3">
<div class="text-xs text-gray-500 mb-1">Tools used:</div>
${output.tools_used.map(tool => `
<span class="tool-badge">
<i data-feather="${tool.icon || 'tool'}" class="w-3 h-3 mr-1"></i>
${tool.name}
</span>
`).join('')}
</div>
` : ''}
<agentic-output-viewer output='${JSON.stringify(output)}'></agentic-output-viewer>
${output.metadata ? `
<div class="provenance-panel">
<div class="provenance-header" onclick="this.nextElementSibling.classList.toggle('hidden')">
<i data-feather="info" class="mr-2"></i>
Provenance & Metadata
<i data-feather="chevron-down" class="ml-auto w-4 h-4"></i>
</div>
<div class="provenance-content hidden">
${output.metadata.version_hash ? `
<div class="mb-2">
<div class="text-xs text-gray-500">Version:</div>
<div class="flex items-center mt-1">
<code class="text-xs bg-gray-100 p-1 rounded mr-2">${output.metadata.version_hash.substring(0, 12)}...</code>
<button class="text-xs text-blue-600 hover:text-blue-800" onclick="navigator.clipboard.writeText('${output.metadata.version_hash}')">Copy</button>
</div>
</div>
` : ''}
${output.metadata.verification_score ? `
<div class="mb-2">
<div class="text-xs text-gray-500">Verification:</div>
<div class="mt-1">
<span class="verification-badge ${output.metadata.verification_score > 0.8 ? 'verification-high' :
output.metadata.verification_score > 0.5 ? 'verification-medium' : 'verification-low'}">
${output.metadata.verification_score > 0.8 ? '✅ High' :
output.metadata.verification_score > 0.5 ? '⚠️ Medium' : '❌ Low'} (${Math.round(output.metadata.verification_score * 100)}%)
</span>
</div>
</div>
` : ''}
${output.metadata.source_citations?.length > 0 ? `
<div>
<div class="text-xs text-gray-500">Citations:</div>
<div class="mt-1">
${output.metadata.source_citations.map(cite => `
<div class="citation-item">
<a href="${cite.url}" target="_blank" class="text-sm text-blue-600 hover:underline">${cite.title}</a>
${cite.description ? `<div class="text-xs text-gray-500 mt-1">${cite.description}</div>` : ''}
</div>
`).join('')}
</div>
</div>
` : ''}
</div>
</div>
` : ''}
` : `
<div class="empty-output">No output yet. Run the cell to see results.</div>
`}
</div>
</div>
</div>
`;
// Add event listener for the run button
const runBtn = this.shadowRoot.querySelector('.run-btn');
runBtn.addEventListener('click', () => {
const cellId = this.getAttribute('id');
mockRunCell(cellId);
});
// Initialize feather icons
if (window.feather) {
window.feather.replace();
}
}
}
customElements.define('agentic-cell', AgenticCell);