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,199 @@
|
||||
/**
|
||||
* Communication plugin API client.
|
||||
*
|
||||
* All requests use the shared `apiClient` (`baseURL: '/api/v1'`) and target the
|
||||
* kommunikation plugin routes under `/comm/...`.
|
||||
*/
|
||||
|
||||
import { apiDelete, apiGet, apiPatch, apiPost } from './client';
|
||||
|
||||
// ─── Types ───
|
||||
|
||||
export interface Participant {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
participant_id: string | null;
|
||||
participant_type: string;
|
||||
display_name: string | null;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string;
|
||||
title: string | null;
|
||||
is_locked: boolean;
|
||||
locked_by: string | null;
|
||||
is_direct: boolean;
|
||||
is_archived: boolean;
|
||||
is_pinned: boolean;
|
||||
created_by: string | null;
|
||||
created_by_type: string;
|
||||
last_msg_at: string | null;
|
||||
last_msg_preview: string | null;
|
||||
last_msg_sender_type: string | null;
|
||||
participants: Participant[];
|
||||
unread_count: number;
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface MessageBlock {
|
||||
id: string;
|
||||
block_type: string;
|
||||
block_data: Record<string, any>;
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
export interface MessageAttachment {
|
||||
id: string;
|
||||
file_id: string | null;
|
||||
file_source: string;
|
||||
file_name: string;
|
||||
file_type: string;
|
||||
file_size: number | null;
|
||||
thumbnail_path: string | null;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
sender_id: string | null;
|
||||
sender_type: string;
|
||||
content: string;
|
||||
content_format: string;
|
||||
metadata: Record<string, any>;
|
||||
reply_to_id: string | null;
|
||||
is_pinned: boolean;
|
||||
created_at: string | null;
|
||||
edited_at: string | null;
|
||||
blocks: MessageBlock[];
|
||||
attachments: MessageAttachment[];
|
||||
reactions: any[];
|
||||
}
|
||||
|
||||
export interface ConversationListResponse {
|
||||
items: Conversation[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface MessageListResponse {
|
||||
items: Message[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export interface BlockType {
|
||||
type: string;
|
||||
label: string;
|
||||
schema: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface MiniApp {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
config_schema: Record<string, any>;
|
||||
}
|
||||
|
||||
// ─── Conversations ───
|
||||
|
||||
export const listConversations = (archived?: boolean) =>
|
||||
apiGet<ConversationListResponse>('/comm/conversations', {
|
||||
params: archived !== undefined ? { archived } : {},
|
||||
});
|
||||
|
||||
export const createConversation = (data: {
|
||||
title?: string;
|
||||
participant_ids?: string[];
|
||||
is_direct?: boolean;
|
||||
initial_message?: string;
|
||||
}) => apiPost<Conversation>('/comm/conversations', data);
|
||||
|
||||
export const getConversation = (id: string) =>
|
||||
apiGet<Conversation>(`/comm/conversations/${id}`);
|
||||
|
||||
export const updateConversation = (id: string, data: { title?: string; is_archived?: boolean }) =>
|
||||
apiPatch<Conversation>(`/comm/conversations/${id}`, data);
|
||||
|
||||
export const leaveConversation = (id: string) =>
|
||||
apiDelete<{ success: boolean }>(`/comm/conversations/${id}`);
|
||||
|
||||
export const pinConversation = (id: string) =>
|
||||
apiPost<{ success: boolean }>(`/comm/conversations/${id}/pin`);
|
||||
|
||||
export const unpinConversation = (id: string) =>
|
||||
apiDelete<{ success: boolean }>(`/comm/conversations/${id}/pin`);
|
||||
|
||||
export const muteConversation = (id: string) =>
|
||||
apiPost<{ success: boolean }>(`/comm/conversations/${id}/mute`);
|
||||
|
||||
export const unmuteConversation = (id: string) =>
|
||||
apiDelete<{ success: boolean }>(`/comm/conversations/${id}/mute`);
|
||||
|
||||
// ─── Participants ───
|
||||
|
||||
export const addParticipant = (
|
||||
convId: string,
|
||||
data: { participant_id: string; participant_type: string; role?: string },
|
||||
) => apiPost<Participant>(`/comm/conversations/${convId}/participants`, data);
|
||||
|
||||
export const removeParticipant = (convId: string, pid: string) =>
|
||||
apiDelete<{ success: boolean }>(`/comm/conversations/${convId}/participants/${pid}`);
|
||||
|
||||
export const changeParticipantRole = (convId: string, pid: string, role: string) =>
|
||||
apiPatch<Participant>(`/comm/conversations/${convId}/participants/${pid}`, { role });
|
||||
|
||||
// ─── Messages ───
|
||||
|
||||
export const getMessages = (convId: string, page = 1, pageSize = 50, before?: string) =>
|
||||
apiGet<MessageListResponse>(`/comm/conversations/${convId}/messages`, {
|
||||
params: { page, page_size: pageSize, ...(before ? { before } : {}) },
|
||||
});
|
||||
|
||||
export const sendMessage = (
|
||||
convId: string,
|
||||
data: {
|
||||
content: string;
|
||||
content_format?: string;
|
||||
blocks?: Array<{ block_type: string; block_data: Record<string, any> }>;
|
||||
reply_to_id?: string;
|
||||
attachments?: Array<Record<string, any>>;
|
||||
},
|
||||
) => apiPost<Message>(`/comm/conversations/${convId}/messages`, data);
|
||||
|
||||
export const editMessage = (id: string, data: { content?: string }) =>
|
||||
apiPatch<Message>(`/comm/messages/${id}`, data);
|
||||
|
||||
export const deleteMessage = (id: string) =>
|
||||
apiDelete<{ success: boolean }>(`/comm/messages/${id}`);
|
||||
|
||||
// ─── Reactions ───
|
||||
|
||||
export const addReaction = (msgId: string, emoji: string) =>
|
||||
apiPost<any>(`/comm/messages/${msgId}/reactions`, { emoji });
|
||||
|
||||
export const removeReaction = (msgId: string, emoji: string) =>
|
||||
apiDelete<{ success: boolean }>(`/comm/messages/${msgId}/reactions/${encodeURIComponent(emoji)}`);
|
||||
|
||||
// ─── Read State ───
|
||||
|
||||
export const markRead = (convId: string, lastReadMsgId?: string) =>
|
||||
apiPost<{ success: boolean }>(`/comm/conversations/${convId}/read`, {
|
||||
last_read_msg_id: lastReadMsgId,
|
||||
});
|
||||
|
||||
// ─── Mini-Apps ───
|
||||
|
||||
export const listMiniApps = () =>
|
||||
apiGet<{ items: MiniApp[] }>('/comm/miniapps');
|
||||
|
||||
export const startMiniApp = (convId: string, appId: string, config?: Record<string, any>) =>
|
||||
apiPost<Message>(`/comm/conversations/${convId}/miniapps`, {
|
||||
app_id: appId,
|
||||
config: config || {},
|
||||
});
|
||||
|
||||
// ─── Block Types ───
|
||||
|
||||
export const getBlockTypes = () =>
|
||||
apiGet<{ items: BlockType[] }>('/comm/block-types');
|
||||
Reference in New Issue
Block a user