feat: Unified Messaging System — kommunikation plugin, AI/Proactive/System participants, MessageSidebar, Rich Content Renderer

Phase 1: Backend plugin kommunikation (13 files, 10 tables, REST API, WebSocket, RBAC, DMS Bridge, Participant Registry, Mini-App Registry, Search Provider)
Phase 2: AI plugins as participants (ai_assistant + ai_proactive dock as participants, heartbeat job)
Phase 3: system_notif plugin (system events → chat messages, pinned System room)
Phase 4: Frontend MessageSidebar (replaces AISidebar, same design, comm API client, WebSocket hook, commStore)
Phase 5: Rich Content Block Renderer (11 components: Markdown, HTML, Image, Audio, Video, File, ActionCard, ContactCard, MiniApp, BlockRenderer)
BasePlugin: added services property + _container in on_activate
This commit is contained in:
Agent Zero
2026-07-22 01:22:15 +02:00
parent 5980d38c66
commit cc3ac9a43d
45 changed files with 8154 additions and 113 deletions
@@ -0,0 +1,85 @@
import React from 'react';
import type { MessageBlock } from '@/store/commStore';
interface FileBlockProps {
block: MessageBlock;
}
function formatFileSize(bytes: number | undefined): string {
if (typeof bytes !== 'number' || bytes <= 0) return '';
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
const fileIcon = (
<svg
className="w-8 h-8 text-secondary-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z M14 2v6h6 M8 13h8 M8 17h5"
/>
</svg>
);
const downloadIcon = (
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
);
const FileBlock: React.FC<FileBlockProps> = ({ block }) => {
const { url, name, size } = block.block_data;
if (!url && !name) {
return null;
}
const fileName: string = name || 'Datei';
const fileSizeLabel = formatFileSize(size);
return (
<div className="flex items-center gap-3 p-3 rounded-lg border border-secondary-200 bg-white shadow-sm">
<div className="flex-shrink-0">{fileIcon}</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-secondary-700 truncate">{fileName}</p>
{fileSizeLabel && (
<p className="text-xs text-secondary-400">{fileSizeLabel}</p>
)}
</div>
{url && (
<a
href={url}
download={fileName}
target="_blank"
rel="noopener noreferrer"
className="flex-shrink-0 p-2 rounded-lg bg-primary-500 text-white hover:bg-primary-600 transition-colors"
aria-label={`Datei herunterladen: ${fileName}`}
>
{downloadIcon}
</a>
)}
</div>
);
};
export default FileBlock;