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:
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import type { MessageBlock } from '@/store/commStore';
|
||||
|
||||
interface ActionCardBlockProps {
|
||||
block: MessageBlock;
|
||||
}
|
||||
|
||||
interface ActionItem {
|
||||
label: string;
|
||||
action: string;
|
||||
type?: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
const ActionCardBlock: React.FC<ActionCardBlockProps> = ({ block }) => {
|
||||
const { title, body, actions } = block.block_data;
|
||||
|
||||
const cardTitle: string = title || '';
|
||||
const cardBody: string = body || '';
|
||||
const actionList: ActionItem[] = Array.isArray(actions) ? actions : [];
|
||||
|
||||
const handleActionClick = (action: ActionItem) => {
|
||||
if (action.action === 'dismiss') {
|
||||
// Frontend handles dismiss — no-op here, parent component can wire up
|
||||
return;
|
||||
}
|
||||
// Treat as URL
|
||||
window.open(action.action, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border border-secondary-200 rounded-lg p-4 shadow-sm bg-white space-y-2">
|
||||
{cardTitle && (
|
||||
<h4 className="text-sm font-semibold text-secondary-800">{cardTitle}</h4>
|
||||
)}
|
||||
{cardBody && (
|
||||
<p className="text-sm text-secondary-600">{cardBody}</p>
|
||||
)}
|
||||
{actionList.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 pt-1">
|
||||
{actionList.map((action, idx) => {
|
||||
const isPrimary = action.type !== 'secondary';
|
||||
return (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => handleActionClick(action)}
|
||||
className={
|
||||
isPrimary
|
||||
? 'px-3 py-1.5 text-sm rounded-lg bg-primary-600 text-white hover:bg-primary-700 transition-colors'
|
||||
: 'px-3 py-1.5 text-sm rounded-lg bg-secondary-100 text-secondary-700 hover:bg-secondary-200 transition-colors'
|
||||
}
|
||||
>
|
||||
{action.label || 'Aktion'}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionCardBlock;
|
||||
Reference in New Issue
Block a user