Alle Unicode-Icons durch Font Awesome 4.7 Icons ersetzt

- chat.js: Attachment-Labels, Streaming-Spinner, File/Ref-Previews, Warning/Error-Bubbles
- chat-page.js: Meta-Bar Status (Erfolg/Fehler), Backup-Meldungen
- dashboard.js: Nachrichten/Ordner/Persona-Icons
- session-page.js: Remove-Buttons
- history.js: Archiv-Badge, Attachment-Indicator
- ui.js: Archiv-Button, Warning/Error-Bubble Icons
- file-explorer.js: Verzeichnis/Datei-Icons, Back/Close-Buttons, getFileIcon()
- index.html: Header-Menü, Send-Button, Settings-Button
This commit is contained in:
Meleki committed 2026-02-28 19:10:24 +01:00
1 parent 8f4fe8a504
commit 8c9f54da79
9 files changed
+62 -56

No files matched your search

+10 -10
View File
@@ -61,7 +61,7 @@
<template id="tpl-session">
<div class="page form-page">
<header>
<button class="back-btn" data-page="dashboard">←</button>
<button class="back-btn" data-page="dashboard"><i class="fa fa-arrow-left"></i></button>
<h1 id="sessionPageTitle">Neue Session</h1>
<button id="openChatBtn" disabled>Chat öffnen</button>
</header>
@@ -82,7 +82,7 @@
<label>Workspace</label>
<div class="input-with-btn">
<input type="text" id="sessionWorkspace" placeholder="Absoluter Pfad zum Arbeitsverzeichnis (optional)">
<button type="button" id="sessionWorkspaceBrowse" class="input-addon-btn">📂</button>
<button type="button" id="sessionWorkspaceBrowse" class="input-addon-btn"><i class="fa fa-folder"></i></button>
</div>
<label>Kontext</label>
<textarea id="sessionContext" rows="4" placeholder="Zusätzlicher Kontext für den System-Prompt (optional)"></textarea>
@@ -102,7 +102,7 @@
<template id="tpl-agent">
<div class="page form-page">
<header>
<button class="back-btn" data-page="dashboard">←</button>
<button class="back-btn" data-page="dashboard"><i class="fa fa-arrow-left"></i></button>
<h1 id="agentPageTitle">Neuer Agent</h1>
</header>
<div class="page-content">
@@ -139,11 +139,11 @@
<template id="tpl-chat">
<div class="page chat-page">
<header>
<button class="back-btn" id="chatBackBtn">←</button>
<button class="back-btn" id="chatBackBtn"><i class="fa fa-arrow-left"></i></button>
<h1 id="sessionTitle">-</h1>
<div class="custom-cmd-bar" id="customCmdBar"></div>
<div class="header-menu">
<button class="header-menu-trigger" id="chatMenuBtn">⋮</button>
<button class="header-menu-trigger" id="chatMenuBtn"><i class="fa fa-ellipsis-v"></i></button>
<div class="header-menu-dropdown" id="chatMenuDropdown" hidden>
<button class="header-menu-item" id="systemPromptBtn">System-Prompt anzeigen</button>
<button class="header-menu-item" id="archiveBtn">Archivieren</button>
@@ -158,10 +158,10 @@
<div id="refPreview" class="ref-preview" hidden></div>
<div class="input-area">
<input type="file" id="fileInput" accept="image/jpeg,image/png,image/gif,image/webp,application/pdf" multiple hidden>
<button id="fileBtn" class="file-btn" title="Datei anhängen">📎</button>
<button id="explorerBtn" class="file-btn" title="Workspace durchsuchen">📂</button>
<button id="fileBtn" class="file-btn" title="Datei anhängen"><i class="fa fa-paperclip"></i></button>
<button id="explorerBtn" class="file-btn" title="Workspace durchsuchen"><i class="fa fa-folder"></i></button>
<textarea id="input" placeholder="Nachricht..." rows="1"></textarea>
<button id="send">→</button>
<button id="send"><i class="fa fa-paper-plane"></i></button>
</div>
</div>
</div>
@@ -170,11 +170,11 @@
<template id="tpl-settings">
<div class="page settings-page">
<header>
<button class="back-btn" data-page="dashboard">←</button>
<button class="back-btn" data-page="dashboard"><i class="fa fa-arrow-left"></i></button>
<h1>Einstellungen</h1>
</header>
<div class="page-content placeholder">
<h2>⚙️ Einstellungen</h2>
<h2><i class="fa fa-cog"></i> Einstellungen</h2>
<p>Hier können diverse Einstellungen getroffen werden.</p>
</div>
</div>
+8 -8
View File
@@ -78,7 +78,7 @@ async function sendMessage() {
source: { type: 'base64', media_type: f.type, data: f.base64 },
_file_name: f.name
});
labels.push((blockType === 'image' ? '📷 ' : '📄 ') + f.name);
labels.push('[' + (blockType === 'image' ? 'Bild' : 'Datei') + ': ' + f.name + ']');
}
if (msgText) content.push({ type: 'text', text: msgText });
bubbleText = labels.join('\n') + (msgText ? '\n' + msgText : '');
@@ -95,7 +95,7 @@ async function sendMessage() {
input.dispatchEvent(new Event('input'))
btn.disabled = true;
isStreaming = true;
setMetaHtml('<span>⏳ Streaming...</span>');
setMetaHtml('<span><i class="fa fa-hourglass-half"></i> Streaming...</span>');
let fullText = '';
let currentToolBox = null;
@@ -160,7 +160,7 @@ async function sendMessage() {
case 'done':
endStreamBubble();
if (data.stopReason === 'max_tokens') {
addWarningBubble('⚠️ Antwort wurde abgeschnitten (max_tokens erreicht). Bitte frage nach dem Rest.', true);
addWarningBubble('Antwort wurde abgeschnitten (max_tokens erreicht). Bitte frage nach dem Rest.', true);
}
// Chain-Meta als streamed markieren (wird im finally ersetzt)
_lastDoneChainData = null;
@@ -188,7 +188,7 @@ async function sendMessage() {
const status = await apiIsStreaming(getCurrentSessionId());
if (status.streaming) {
// Backend streamt noch weiter – Recovery-Poll starten
setMetaHtml('<span>⏳ Streaming... (Verbindung wiederhergestellt)</span>');
setMetaHtml('<span><i class="fa fa-hourglass-half"></i> Streaming... (Verbindung wiederhergestellt)</span>');
startStreamingPoll(getCurrentSessionId(), btn, status.chainId);
return;
}
@@ -251,9 +251,9 @@ function renderFilePreview() {
preview.innerHTML = pendingFiles.map((f, i) => {
const isImage = f.type.startsWith('image/');
return `<div class="file-preview-inner">
${isImage ? '<img src="data:' + f.type + ';base64,' + f.base64 + '" class="file-thumb">' : '<span class="file-icon">📄</span>'}
${isImage ? '<img src="data:' + f.type + ';base64,' + f.base64 + '" class="file-thumb">' : '<span class="file-icon"><i class="fa fa-file-text-o"></i></span>'}
<span class="file-name">${esc(f.name)}</span>
<button class="file-remove" onclick="removePendingFile(${i})">✕</button>
<button class="file-remove" onclick="removePendingFile(${i})"><i class="fa fa-times"></i></button>
</div>`;
}).join('');
preview.hidden = false;
@@ -300,9 +300,9 @@ function renderRefPreview() {
container.innerHTML = pendingRefs.map((r, i) => {
const label = r.from && r.to ? `${r.path}:${r.from}-${r.to}` : r.path;
return `<div class="ref-preview-item">
<span class="ref-icon">📂</span>
<span class="ref-icon"><i class="fa fa-folder-open"></i></span>
<span class="ref-label">${esc(label)}</span>
<button class="file-remove" onclick="removePendingRef(${i})">✕</button>
<button class="file-remove" onclick="removePendingRef(${i})"><i class="fa fa-times"></i></button>
</div>`;
}).join('');
container.hidden = false;
+22 -16
View File
@@ -26,10 +26,10 @@ function openFileExplorer(opts = {}) {
modal.innerHTML = `
<div class="fe-header">
<div class="fe-nav">
<button class="fe-btn" id="feBack" title="Zurück" disabled>←</button>
<button class="fe-btn" id="feBack" title="Zurück" disabled><i class="fa fa-arrow-left"></i></button>
<span class="fe-path" id="fePath">/</span>
</div>
<button class="fe-btn" id="feClose">✕</button>
<button class="fe-btn" id="feClose"><i class="fa fa-times"></i></button>
</div>
<div class="fe-body">
<div class="fe-sidebar" id="feSidebar">
@@ -115,7 +115,7 @@ async function feLoadDir(path) {
if (path) {
const upItem = document.createElement('div');
upItem.className = 'fe-item fe-dir';
upItem.textContent = '📁 ..';
upItem.innerHTML = '<i class="fa fa-folder"></i> ..';
upItem.addEventListener('click', () => {
_feState.history.push(path);
const parent = path.includes('/') ? path.substring(0, path.lastIndexOf('/')) : '';
@@ -130,15 +130,15 @@ async function feLoadDir(path) {
if (entry.type === 'dir') {
item.classList.add('fe-dir');
item.textContent = '📁 ' + entry.name;
item.innerHTML = '<i class="fa fa-folder"></i> ' + esc(entry.name);
item.addEventListener('click', () => {
_feState.history.push(path);
feLoadDir(entry.path);
});
} else {
item.classList.add('fe-file');
const icon = entry.isText ? '📄' : getFileIcon(entry.ext);
item.textContent = icon + ' ' + entry.name;
const icon = entry.isText ? '<i class="fa fa-file-text-o"></i>' : getFileIcon(entry.ext);
item.innerHTML = icon + ' ' + esc(entry.name);
item.dataset.path = entry.path;
item.dataset.isText = entry.isText;
item.addEventListener('click', () => feSelectFile(entry, item));
@@ -197,7 +197,7 @@ async function feSelectFile(entry, itemEl) {
feDestroyEditor();
if (!entry.isText) {
main.innerHTML = `<div class="fe-placeholder">${getFileIcon(entry.ext)} ${entry.name}<br><small>${formatSize(entry.size)} - Vorschau nicht verfügbar</small></div>`;
main.innerHTML = `<div class="fe-placeholder">${getFileIcon(entry.ext)} ${esc(entry.name)}<br><small>${formatSize(entry.size)} - Vorschau nicht verfügbar</small></div>`;
footer.hidden = true;
_feState.selectedFile = null;
return;
@@ -485,10 +485,10 @@ function openFolderBrowser(opts = {}) {
modal.innerHTML = `
<div class="fe-header">
<div class="fe-nav">
<button class="fe-btn" id="feBack" title="Zurück" disabled>←</button>
<button class="fe-btn" id="feBack" title="Zurück" disabled><i class="fa fa-arrow-left"></i></button>
<span class="fe-path" id="fePath">/</span>
</div>
<button class="fe-btn" id="feClose">✕</button>
<button class="fe-btn" id="feClose"><i class="fa fa-times"></i></button>
</div>
<div class="fe-body">
<div class="fe-sidebar fe-sidebar-full" id="feSidebar">
@@ -546,7 +546,7 @@ async function fbLoadDir(absPath) {
for (const entry of data.entries) {
const item = document.createElement('div');
item.className = 'fe-item fe-dir';
item.textContent = '📁 ' + entry.name;
item.innerHTML = '<i class="fa fa-folder"></i> ' + esc(entry.name);
item.addEventListener('click', () => {
_feState.history.push(_feState.currentAbsPath);
fbLoadDir(entry.path);
@@ -574,13 +574,19 @@ function fbGoBack() {
function getFileIcon(ext) {
const icons = {
'.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️', '.webp': '🖼️', '.svg': '🖼️',
'.pdf': '📑', '.zip': '📦', '.gz': '📦', '.tar': '📦',
'.mp3': '🎵', '.wav': '🎵', '.mp4': '🎬', '.avi': '🎬',
'.exe': '⚙️', '.dll': '⚙️', '.so': '⚙️',
'.db': '🗃️', '.sqlite': '🗃️'
'.png': '<i class="fa fa-image"></i>', '.jpg': '<i class="fa fa-image"></i>',
'.jpeg': '<i class="fa fa-image"></i>', '.gif': '<i class="fa fa-image"></i>',
'.webp': '<i class="fa fa-image"></i>', '.svg': '<i class="fa fa-image"></i>',
'.pdf': '<i class="fa fa-file-pdf-o"></i>',
'.zip': '<i class="fa fa-archive"></i>', '.gz': '<i class="fa fa-archive"></i>',
'.tar': '<i class="fa fa-archive"></i>',
'.mp3': '<i class="fa fa-file-audio-o"></i>', '.wav': '<i class="fa fa-file-audio-o"></i>',
'.mp4': '<i class="fa fa-file-video-o"></i>', '.avi': '<i class="fa fa-file-video-o"></i>',
'.exe': '<i class="fa fa-cog"></i>', '.dll': '<i class="fa fa-cog"></i>',
'.so': '<i class="fa fa-cog"></i>',
'.db': '<i class="fa fa-database"></i>', '.sqlite': '<i class="fa fa-database"></i>'
};
return icons[ext?.toLowerCase()] || '📄';
return icons[ext?.toLowerCase()] || '<i class="fa fa-file-text-o"></i>';
}
function formatSize(bytes) {
+2 -2
View File
@@ -104,7 +104,7 @@ async function loadLatestArchive() {
const sep = document.createElement('div');
sep.className = 'msg system-info';
sep.textContent = `📦 Archiv vom ${new Date(latest.createdAt).toLocaleString('de-AT')} (${latest.chainCount} Chains, ${latest.messageCount} Messages)`;
sep.innerHTML = `<i class="fa fa-archive"></i> Archiv vom ${new Date(latest.createdAt).toLocaleString('de-AT')} (${latest.chainCount} Chains, ${latest.messageCount} Messages)`;
fragment.appendChild(sep);
for (const entry of data) {
@@ -125,7 +125,7 @@ async function loadLatestArchive() {
const nameList = names.filter(n => n).join(', ');
const ind = document.createElement('div');
ind.className = 'attachment-indicator';
ind.textContent = `📎 ${msg.attachmentCount} Datei${msg.attachmentCount > 1 ? 'en' : ''}` + (nameList ? `: ${nameList}` : '');
ind.innerHTML = `<i class="fa fa-paperclip"></i> ${msg.attachmentCount} Datei${msg.attachmentCount > 1 ? 'en' : ''}` + (nameList ? `: ${esc(nameList)}` : '');
ind.style.cursor = 'pointer';
ind.title = 'Klicken zum Anzeigen';
ind.addEventListener('click', () => loadAndShowAttachments(msg.id, ind));
+3 -3
View File
@@ -195,10 +195,10 @@ registerPage('chat', {
backupDbBtn.disabled = true;
try {
await apiBackupDb();
setMeta('✅ Backup erstellt und heruntergeladen');
setMetaHtml('<i class="fa fa-check-circle"></i> Backup erstellt und heruntergeladen');
} catch (e) {
console.error('Backup fehlgeschlagen:', e);
setMeta('❌ Backup fehlgeschlagen');
setMetaHtml('<i class="fa fa-times-circle"></i> Backup fehlgeschlagen');
} finally {
backupDbBtn.disabled = false;
}
@@ -223,7 +223,7 @@ registerPage('chat', {
if (status.streaming) {
isStreaming = true;
if (sendBtn) sendBtn.disabled = true;
setMetaHtml('<span>⏳ Streaming...</span>');
setMetaHtml('<span><i class="fa fa-hourglass-half"></i> Streaming...</span>');
startStreamingPoll(sessionId, sendBtn, status.chainId);
}
} catch (e) {
+3 -3
View File
@@ -29,8 +29,8 @@ registerPage('dashboard', {
</div>
${s.description ? `<div class="card-desc">${esc(s.description)}</div>` : ''}
<div class="card-meta">
${msgLabel ? `<span class="card-meta-item">💬 ${esc(msgLabel)}</span>` : ''}
${workspaceLabel ? `<span class="card-meta-item card-workspace" title="${esc(workspaceLabel)}">📁 ${esc(workspaceLabel)}</span>` : ''}
${msgLabel ? `<span class="card-meta-item"><i class="fa fa-comment"></i> ${esc(msgLabel)}</span>` : ''}
${workspaceLabel ? `<span class="card-meta-item card-workspace" title="${esc(workspaceLabel)}"><i class="fa fa-folder"></i> ${esc(workspaceLabel)}</span>` : ''}
</div>
</div>
`;
@@ -60,7 +60,7 @@ registerPage('dashboard', {
<span class="badge">${esc(a.model)}</span>
</div>
${a.description ? `<div class="card-desc">${esc(a.description)}</div>` : ''}
${a.persona ? `<div class="card-meta"><span class="card-meta-item card-persona">🎭 ${esc(a.persona)}</span></div>` : ''}
${a.persona ? `<div class="card-meta"><span class="card-meta-item card-persona"><i class="fa fa-user-secret"></i> ${esc(a.persona)}</span></div>` : ''}
</div>
`;
card.addEventListener('click', () => navigateTo('agent', { id: a.agentId }));
+1 -1
View File
@@ -11,7 +11,7 @@ function _createCommandRow(container, name = '', command = '') {
<input type="text" class="cmd-name" placeholder="Name (z.B. Deploy)" value="${_escAttr(name)}">
<input type="text" class="cmd-command" placeholder="Befehl (z.B. ./deploy.sh)" value="${_escAttr(command)}">
</div>
<button type="button" class="remove-cmd-btn">✕</button>
<button type="button" class="remove-cmd-btn"><i class="fa fa-times"></i></button>
`;
item.querySelector('.remove-cmd-btn').addEventListener('click', () => item.remove());
container.appendChild(item);
+11 -11
View File
@@ -57,7 +57,7 @@ function renderUserContent(container, text) {
// Schöne Darstellung: Icon + Kurzpfad
const fileName = filePath.includes('/') ? filePath.substring(filePath.lastIndexOf('/') + 1) : filePath;
const lineInfo = ref.replace(filePath, '').trim(); // z.B. ":10-25"
chip.textContent = '📄 ' + fileName + lineInfo;
chip.innerHTML = '<i class="fa fa-file-text-o"></i> ' + esc(fileName + lineInfo);
chip.title = ref;
chip.style.cursor = 'pointer';
chip.addEventListener('click', () => {
@@ -98,7 +98,7 @@ function addErrorBubble(text, streamed = false) {
div.className = 'msg system-info';
if (streamed) div.classList.add('streamed');
div.style.color = 'var(--dangerous)';
div.textContent = (text || '').trim();
div.innerHTML = '<i class="fa fa-times-circle"></i> ' + esc((text || '').trim());
chatEl().appendChild(div);
scrollDown();
}
@@ -108,7 +108,7 @@ function addWarningBubble(text, streamed = false) {
div.className = 'msg system-info';
if (streamed) div.classList.add('streamed');
div.style.color = 'var(--warning)';
div.textContent = (text || '').trim();
div.innerHTML = '<i class="fa fa-exclamation-triangle"></i> ' + esc((text || '').trim());
chatEl().appendChild(div);
scrollDown();
}
@@ -153,7 +153,7 @@ function endStreamBubble() {
function addToolBoxLoading(name) {
const div = document.createElement('div');
div.className = 'tool-box streamed';
div.innerHTML = '<span class="tool-name">' + esc(name) + '</span> <span class="tool-spinner">⏳</span>';
div.innerHTML = '<span class="tool-name">' + esc(name) + '</span> <span class="tool-spinner"><i class="fa fa-hourglass-half"></i></span>';
chatEl().appendChild(div);
scrollDown();
return div;
@@ -365,7 +365,7 @@ function addAttachmentIndicator(messageId, count, namesJson) {
indicator.className = 'attachment-indicator';
const nameList = names.filter(n => n).join(', ');
indicator.textContent = `📎 ${count} Datei${count > 1 ? 'en' : ''}` + (nameList ? `: ${nameList}` : '');
indicator.innerHTML = `<i class="fa fa-paperclip"></i> ${count} Datei${count > 1 ? 'en' : ''}` + (nameList ? `: ${esc(nameList)}` : '');
indicator.style.cursor = 'pointer';
indicator.title = 'Klicken zum Anzeigen';
indicator.addEventListener('click', () => loadAndShowAttachments(messageId, indicator));
@@ -375,7 +375,7 @@ function addAttachmentIndicator(messageId, count, namesJson) {
async function loadAndShowAttachments(messageId, indicator) {
if (indicator.dataset.loaded) return;
indicator.dataset.loaded = '1';
indicator.textContent = '📎 Laden...';
indicator.innerHTML = '<i class="fa fa-paperclip"></i> Laden...';
try {
const resp = await fetch(`/api/attachments/${messageId}`);
@@ -396,15 +396,15 @@ async function loadAndShowAttachments(messageId, indicator) {
link.href = `data:${att.mediaType};base64,${att.data}`;
link.download = att.fileName || 'datei';
link.className = 'attachment-file';
link.textContent = `📄 ${att.fileName || 'Dokument'}`;
link.innerHTML = `<i class="fa fa-file-text-o"></i> ${esc(att.fileName || 'Dokument')}`;
container.appendChild(link);
}
}
indicator.textContent = `📎 ${attachments.length} Datei${attachments.length > 1 ? 'en' : ''}`;
indicator.innerHTML = `<i class="fa fa-paperclip"></i> ${attachments.length} Datei${attachments.length > 1 ? 'en' : ''}`;
indicator.parentElement.appendChild(container);
} catch (e) {
indicator.textContent = '📎 Fehler beim Laden';
indicator.innerHTML = '<i class="fa fa-paperclip"></i> Fehler beim Laden';
indicator.dataset.loaded = '';
}
}
@@ -426,7 +426,7 @@ async function showSystemPromptModal(sessionId) {
const title = document.createElement('span');
title.textContent = 'System-Prompt';
const closeBtn = document.createElement('button');
closeBtn.textContent = '✕';
closeBtn.innerHTML = '<i class="fa fa-times"></i>';
closeBtn.className = 'sp-close';
header.appendChild(title);
header.appendChild(closeBtn);
@@ -479,7 +479,7 @@ function showArchiveLoadButton() {
const btn = document.createElement('button');
btn.className = 'archive-load-btn';
btn.textContent = '📦 Archivierte Nachrichten laden';
btn.innerHTML = '<i class="fa fa-archive"></i> Archivierte Nachrichten laden';
btn.addEventListener('click', loadLatestArchive);
// Immer ganz oben im Chat