Update agents, dashboard, kb +2 more (+14 ~20 -3)

This commit is contained in:
Echo
2026-01-31 13:36:24 +00:00
parent a44b9ef852
commit 6555ea28ee
34 changed files with 1919 additions and 225 deletions

View File

@@ -102,6 +102,149 @@
height: 16px;
}
.sort-select {
background: transparent;
border: none;
color: var(--text-secondary);
font-size: var(--text-sm);
padding: var(--space-1) var(--space-2);
cursor: pointer;
outline: none;
}
.sort-select option {
background: var(--bg-base);
color: var(--text-primary);
}
/* ========== LIST VIEW - Windows Explorer style ========== */
.file-grid.view-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: var(--space-1);
}
.file-grid.view-list .file-item {
flex-direction: row;
padding: var(--space-1) var(--space-2);
gap: var(--space-2);
background: transparent;
border: none;
}
.file-grid.view-list .file-item:hover {
background: var(--bg-surface-hover);
}
.file-grid.view-list .file-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.file-grid.view-list .file-icon svg {
width: 16px;
height: 16px;
}
.file-grid.view-list .file-name {
font-size: var(--text-sm);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-grid.view-list .file-meta {
display: none;
}
/* ========== DETAILS VIEW - Windows Explorer style with columns ========== */
.file-grid.view-details {
display: flex;
flex-direction: column;
gap: 0;
}
.file-grid.view-details .file-header {
display: grid;
grid-template-columns: 24px 1fr 100px 80px 120px;
align-items: center;
padding: var(--space-2) var(--space-3);
gap: var(--space-3);
background: var(--bg-surface);
border-bottom: 2px solid var(--border);
font-size: var(--text-xs);
font-weight: 600;
color: var(--text-muted);
position: sticky;
top: 0;
}
.file-grid.view-details .file-header span {
cursor: pointer;
}
.file-grid.view-details .file-header span:hover {
color: var(--text-primary);
}
.file-grid.view-details .file-item {
display: grid;
grid-template-columns: 24px 1fr 100px 80px 120px;
align-items: center;
padding: var(--space-2) var(--space-3);
gap: var(--space-3);
background: transparent;
border: none;
border-bottom: 1px solid var(--border);
border-radius: 0;
}
.file-grid.view-details .file-item:hover {
background: var(--bg-surface-hover);
}
.file-grid.view-details .file-icon {
width: 18px;
height: 18px;
}
.file-grid.view-details .file-icon svg {
width: 18px;
height: 18px;
}
.file-grid.view-details .file-name {
font-size: var(--text-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.file-grid.view-details .file-meta {
display: contents;
}
.file-grid.view-details .file-type,
.file-grid.view-details .file-size,
.file-grid.view-details .file-date {
font-size: var(--text-xs);
color: var(--text-muted);
white-space: nowrap;
}
.file-grid.view-details .file-type {
text-transform: uppercase;
}
/* ========== TILES VIEW - Original grid style ========== */
.file-grid.view-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: var(--space-3);
}
/* Content area */
.content-area {
flex: 1;
@@ -269,6 +412,8 @@
#markdownPreview code { background: var(--bg-surface); padding: 2px 6px; border-radius: 4px; font-family: var(--font-mono); }
#markdownPreview pre { background: var(--bg-surface); padding: 1em; border-radius: 8px; overflow-x: auto; }
#markdownPreview blockquote { border-left: 3px solid var(--accent); padding-left: 1em; margin-left: 0; color: var(--text-muted); }
#markdownPreview a, #markdownPreview .file-link { color: var(--accent); text-decoration: none; }
#markdownPreview a:hover, #markdownPreview .file-link:hover { text-decoration: underline; }
.preview-active #codeEditor { display: none; }
.preview-active #markdownPreview { display: block; }
@@ -348,9 +493,36 @@
<span class="breadcrumb-item current" onclick="loadPath('')">~/clawd</span>
</div>
<div class="toolbar-actions">
<!-- View Mode Toggle -->
<div class="view-toggle" id="viewModeToggle">
<button class="view-btn" data-view="list" onclick="setViewMode('list')" title="Listă">
<i data-lucide="list"></i>
</button>
<button class="view-btn" data-view="details" onclick="setViewMode('details')" title="Detalii">
<i data-lucide="layout-list"></i>
</button>
<button class="view-btn active" data-view="tiles" onclick="setViewMode('tiles')" title="Tiles">
<i data-lucide="layout-grid"></i>
</button>
</div>
<!-- Sort Toggle -->
<div class="view-toggle">
<select class="sort-select" id="sortBy" onchange="sortFiles()">
<option value="name">Nume</option>
<option value="type">Tip</option>
<option value="size">Mărime</option>
<option value="date">Dată</option>
</select>
<button class="view-btn" id="sortDirBtn" onclick="toggleSortDir()" title="Ordine">
<i data-lucide="arrow-down-a-z" id="sortDirIcon"></i>
</button>
</div>
<!-- Browse/Editor Toggle -->
<div class="view-toggle">
<button class="view-btn active" id="browseBtn" onclick="showBrowse()" title="Browse">
<i data-lucide="layout-grid"></i>
<i data-lucide="folder"></i>
</button>
<button class="view-btn" id="editorBtn" onclick="showEditor()" title="Editor">
<i data-lucide="code"></i>
@@ -435,6 +607,73 @@
let currentFile = null;
let originalContent = '';
let isModified = false;
let currentViewMode = localStorage.getItem('filesViewMode') || 'tiles';
let currentSortBy = localStorage.getItem('filesSortBy') || 'name';
let currentSortDir = localStorage.getItem('filesSortDir') || 'asc';
let currentItems = [];
// Initialize view mode
function initViewMode() {
setViewMode(currentViewMode, false);
document.getElementById('sortBy').value = currentSortBy;
updateSortIcon();
}
function setViewMode(mode, reload = true) {
currentViewMode = mode;
localStorage.setItem('filesViewMode', mode);
// Update buttons
document.querySelectorAll('#viewModeToggle .view-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.view === mode);
});
// Update grid class
const grid = document.getElementById('fileGrid');
grid.classList.remove('view-list', 'view-details', 'view-tiles');
grid.classList.add('view-' + mode);
if (reload && currentItems.length > 0) {
renderFileGrid(currentItems);
}
}
function sortFiles() {
currentSortBy = document.getElementById('sortBy').value;
localStorage.setItem('filesSortBy', currentSortBy);
if (currentItems.length > 0) {
renderFileGrid(currentItems);
}
}
function setSortBy(field) {
if (currentSortBy === field) {
toggleSortDir();
} else {
currentSortBy = field;
document.getElementById('sortBy').value = field;
localStorage.setItem('filesSortBy', currentSortBy);
if (currentItems.length > 0) {
renderFileGrid(currentItems);
}
}
}
function toggleSortDir() {
currentSortDir = currentSortDir === 'asc' ? 'desc' : 'asc';
localStorage.setItem('filesSortDir', currentSortDir);
updateSortIcon();
if (currentItems.length > 0) {
renderFileGrid(currentItems);
}
}
function updateSortIcon() {
const icon = document.getElementById('sortDirIcon');
const iconName = currentSortDir === 'asc' ? 'arrow-down-a-z' : 'arrow-up-z-a';
icon.setAttribute('data-lucide', iconName);
lucide.createIcons();
}
function showBrowse() {
if (isModified && !confirm('Ai modificări nesalvate. Continui?')) return;
@@ -498,7 +737,12 @@
}
function renderFileGrid(items) {
// Store items for re-rendering on view/sort change
currentItems = items;
const grid = document.getElementById('fileGrid');
grid.classList.remove('view-list', 'view-details', 'view-tiles');
grid.classList.add('view-' + currentViewMode);
if (items.length === 0) {
grid.innerHTML = `
@@ -511,20 +755,82 @@
return;
}
items.sort((a, b) => {
// Sort items
const getExt = (name) => name.includes('.') ? name.split('.').pop().toLowerCase() : '';
const sorted = [...items].sort((a, b) => {
// Directories always first
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
return a.name.localeCompare(b.name);
let cmp = 0;
if (currentSortBy === 'name') {
cmp = a.name.localeCompare(b.name);
} else if (currentSortBy === 'date') {
cmp = (a.mtime || 0) - (b.mtime || 0);
} else if (currentSortBy === 'size') {
cmp = (a.size || 0) - (b.size || 0);
} else if (currentSortBy === 'type') {
cmp = getExt(a.name).localeCompare(getExt(b.name));
}
return currentSortDir === 'asc' ? cmp : -cmp;
});
grid.innerHTML = items.map(item => `
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
// Add header for details view
let headerHtml = '';
if (currentViewMode === 'details') {
const arrow = (field) => currentSortBy === field ? (currentSortDir === 'asc' ? '' : ' ▼') : '';
headerHtml = `
<div class="file-header">
<span></span>
<span onclick="setSortBy('name')" style="cursor:pointer">Nume${arrow('name')}</span>
<span onclick="setSortBy('type')" style="cursor:pointer">Tip${arrow('type')}</span>
<span onclick="setSortBy('size')" style="cursor:pointer">Mărime${arrow('size')}</span>
<span onclick="setSortBy('date')" style="cursor:pointer">Dată${arrow('date')}</span>
</div>
<div class="file-name">${item.name}</div>
${item.size ? `<div class="file-size">${formatSize(item.size)}</div>` : ''}
</div>
`).join('');
`;
}
grid.innerHTML = headerHtml + sorted.map(item => {
const dateStr = item.mtime ? new Date(item.mtime * 1000).toLocaleString('ro-RO', {
day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit'
}) : '';
const fileType = item.type === 'dir' ? 'Folder' : getFileType(item.name);
const sizeStr = item.size !== undefined ? formatSize(item.size) : '-';
if (currentViewMode === 'details') {
return `
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
</div>
<div class="file-name">${item.name}</div>
<div class="file-meta">
<span class="file-type">${fileType}</span>
<span class="file-size">${sizeStr}</span>
<span class="file-date">${dateStr || '-'}</span>
</div>
</div>
`;
} else if (currentViewMode === 'list') {
return `
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
</div>
<div class="file-name">${item.name}</div>
</div>
`;
} else {
// Tiles view - original style
return `
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
</div>
<div class="file-name">${item.name}</div>
</div>
`;
}
}).join('');
lucide.createIcons();
}
@@ -549,6 +855,26 @@
return icons[ext] || 'file';
}
function getFileType(name) {
const ext = name.split('.').pop().toLowerCase();
const types = {
'md': 'Markdown',
'txt': 'Text',
'json': 'JSON',
'js': 'JavaScript',
'py': 'Python',
'html': 'HTML',
'css': 'CSS',
'sh': 'Shell',
'yml': 'YAML',
'yaml': 'YAML',
'log': 'Log',
'xsd': 'XML Schema',
'pdf': 'PDF'
};
return types[ext] || ext.toUpperCase();
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
@@ -701,6 +1027,7 @@
});
// Init
initViewMode();
loadPath(getPathFromURL());
</script>
</body>