Menu
Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f
AkurAI-Framework / site/frontend/ai/components/artifacts/data_table.js
Latest change 8847e6f4a315fb27eba8cb69b87397dd08a068eb - feat(ai/artifacts): add Chart, DataTable, CodeBlock, CodeDiff ES modules by Ólafur Búi Ólafsson
/**
* Artifact: Data Table
* Payload: { columns: string[], rows: any[][] }
* Renders a sortable table with zebra rows and numeric right-alignment.
*/
function isNumericCol(rows, ci) {
return rows.every(row => {
const v = row[ci];
return v === null || v === undefined || v === '' || !isNaN(Number(v));
});
}
export const def = {
name: 'data_table',
category: 'Artifacts',
title: 'Data Table',
match(p) {
return p != null && Array.isArray(p.columns) && Array.isArray(p.rows);
},
render(payload) {
const { columns, rows } = payload;
const numericCol = columns.map((_, ci) => isNumericCol(rows, ci));
let sortCol = -1;
let sortAsc = true;
let sorted = rows.slice();
const wrap = document.createElement('div');
wrap.style.overflowX = 'auto';
const table = document.createElement('table');
table.className = 'table';
/* Head */
const thead = document.createElement('thead');
const headRow = document.createElement('tr');
columns.forEach((col, ci) => {
const th = document.createElement('th');
if (numericCol[ci]) th.style.textAlign = 'right';
th.style.cursor = 'pointer';
th.style.userSelect = 'none';
th.title = 'Sort';
const label = document.createElement('span');
label.textContent = String(col);
const arrow = document.createElement('span');
arrow.style.cssText = 'margin-left:4px;color:var(--dim)';
arrow.textContent = ' ⇅';
th.appendChild(label);
th.appendChild(arrow);
th.addEventListener('click', () => {
if (sortCol === ci) {
sortAsc = !sortAsc;
} else {
sortCol = ci;
sortAsc = true;
}
headRow.querySelectorAll('th').forEach((h, i) => {
const a = h.querySelector('span:last-child');
if (i === sortCol) {
a.textContent = sortAsc ? ' ↑' : ' ↓';
a.style.color = 'var(--accent)';
} else {
a.textContent = ' ⇅';
a.style.color = 'var(--dim)';
}
});
rebuildBody();
});
headRow.appendChild(th);
});
thead.appendChild(headRow);
table.appendChild(thead);
const tbody = document.createElement('tbody');
table.appendChild(tbody);
wrap.appendChild(table);
function rebuildBody() {
if (sortCol >= 0) {
sorted = rows.slice().sort((a, b) => {
let av = a[sortCol], bv = b[sortCol];
if (numericCol[sortCol]) {
av = Number(av);
bv = Number(bv);
} else {
av = String(av ?? '');
bv = String(bv ?? '');
}
if (av < bv) return sortAsc ? -1 : 1;
if (av > bv) return sortAsc ? 1 : -1;
return 0;
});
} else {
sorted = rows.slice();
}
tbody.textContent = '';
sorted.forEach((row, ri) => {
const tr = document.createElement('tr');
if (ri % 2 === 1) {
tr.style.background = 'color-mix(in srgb, var(--panel) 40%, transparent)';
}
columns.forEach((_, ci) => {
const td = document.createElement('td');
if (numericCol[ci]) {
td.style.textAlign = 'right';
td.style.fontVariantNumeric = 'tabular-nums';
td.style.fontFamily = 'var(--mono)';
}
const v = row[ci];
td.textContent = v == null ? '' : String(v);
tr.appendChild(td);
});
tbody.appendChild(tr);
});
}
rebuildBody();
return wrap;
},
example: {
columns: ['Name', 'Score', 'Rank'],
rows: [
['Alice', 92, 1],
['Bob', 87, 2],
['Carol', 78, 3],
['David', 95, 0]
]
}
};