@natoe/colab 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -0
- package/dist/index.d.mts +865 -0
- package/dist/index.d.ts +865 -0
- package/dist/index.js +4932 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4893 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Channel } from 'phoenix';
|
|
4
|
+
|
|
5
|
+
interface CollabConfig {
|
|
6
|
+
/** Phoenix WebSocket URL (e.g. wss://backend.natoe.ai/socket) */
|
|
7
|
+
socketUrl: string;
|
|
8
|
+
/** Returns the current JWT token for authentication */
|
|
9
|
+
getToken: () => string;
|
|
10
|
+
/** Current authenticated user */
|
|
11
|
+
userId: string;
|
|
12
|
+
userRole: UserRole;
|
|
13
|
+
userName: string;
|
|
14
|
+
userAvatar?: string;
|
|
15
|
+
/** Host app callbacks — colab calls these, never implements them */
|
|
16
|
+
onOpenDicom?: (studyId: string, storageId: string) => void;
|
|
17
|
+
onUploadFile?: (file: File | Blob, fileName?: string) => Promise<string>;
|
|
18
|
+
onDeepLink?: (path: string) => void;
|
|
19
|
+
onError?: (error: CollabError) => void;
|
|
20
|
+
}
|
|
21
|
+
type UserRole = 'lab' | 'radiologist' | 'physician' | 'admin';
|
|
22
|
+
interface PatientData {
|
|
23
|
+
patientName: string;
|
|
24
|
+
patientAge?: string;
|
|
25
|
+
patientSex?: string;
|
|
26
|
+
studyType?: string;
|
|
27
|
+
studyId?: string;
|
|
28
|
+
storageId?: string;
|
|
29
|
+
referringPhysician?: string;
|
|
30
|
+
orderId: string;
|
|
31
|
+
displayOrderId?: string;
|
|
32
|
+
labName?: string;
|
|
33
|
+
bodyParts?: string[];
|
|
34
|
+
}
|
|
35
|
+
interface Conversation {
|
|
36
|
+
id: string;
|
|
37
|
+
orderId: string;
|
|
38
|
+
name: string;
|
|
39
|
+
picture?: string;
|
|
40
|
+
participants: Participant[];
|
|
41
|
+
lastMessage?: Message;
|
|
42
|
+
unreadCount: number;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
/** Denormalized patient data snapshot — lets UI render without extra fetch */
|
|
46
|
+
patientSnapshot?: PatientData;
|
|
47
|
+
/** Currently pinned messages (max 3) */
|
|
48
|
+
pinnedMessages?: Message[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Lightweight item for the inbox conversation list.
|
|
52
|
+
* Sorted by the backend, typically by last activity descending.
|
|
53
|
+
*/
|
|
54
|
+
interface ConversationListItem$1 {
|
|
55
|
+
id: string;
|
|
56
|
+
orderId: string;
|
|
57
|
+
name: string;
|
|
58
|
+
picture?: string;
|
|
59
|
+
unreadCount: number;
|
|
60
|
+
lastMessage?: Message;
|
|
61
|
+
lastActivityAt: string;
|
|
62
|
+
patientSnapshot?: PatientData;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Lightweight preview of a conversation for inline rendering in table rows.
|
|
66
|
+
* Fetched in batch via requestPreview to avoid N+1 queries.
|
|
67
|
+
*/
|
|
68
|
+
interface ConversationPreview {
|
|
69
|
+
conversationId: string;
|
|
70
|
+
orderId: string;
|
|
71
|
+
messageCount: number;
|
|
72
|
+
unreadCount: number;
|
|
73
|
+
/** Last N messages (typically 5) for inline display */
|
|
74
|
+
lastMessages: Message[];
|
|
75
|
+
participants: Participant[];
|
|
76
|
+
name: string;
|
|
77
|
+
}
|
|
78
|
+
/** Batch preview response shape from the backend */
|
|
79
|
+
interface PreviewBatchResponse {
|
|
80
|
+
/** Key is orderId, value is preview or null if no conversation exists */
|
|
81
|
+
previews: Record<string, ConversationPreview | null>;
|
|
82
|
+
}
|
|
83
|
+
/** Response from createConversationWithMessage (first-message flow) */
|
|
84
|
+
interface CreateConversationResponse {
|
|
85
|
+
conversation: Conversation;
|
|
86
|
+
message: Message;
|
|
87
|
+
}
|
|
88
|
+
interface Participant {
|
|
89
|
+
userId: string;
|
|
90
|
+
userName: string;
|
|
91
|
+
userRole: UserRole;
|
|
92
|
+
avatar?: string;
|
|
93
|
+
isOnline?: boolean;
|
|
94
|
+
joinedAt: string;
|
|
95
|
+
}
|
|
96
|
+
type MessageType = 'text' | 'audio' | 'image' | 'file' | 'system' | 'deep_link';
|
|
97
|
+
/**
|
|
98
|
+
* Denormalized snapshot of a message at the time it was quoted.
|
|
99
|
+
* Preserved on replies so the quoted context survives even if the original
|
|
100
|
+
* message is later deleted or edited.
|
|
101
|
+
*/
|
|
102
|
+
interface MessageSnapshot {
|
|
103
|
+
messageId: string;
|
|
104
|
+
senderId: string;
|
|
105
|
+
senderName: string;
|
|
106
|
+
body: string;
|
|
107
|
+
type: MessageType;
|
|
108
|
+
}
|
|
109
|
+
interface Message {
|
|
110
|
+
id: string;
|
|
111
|
+
conversationId: string;
|
|
112
|
+
senderId: string;
|
|
113
|
+
senderName: string;
|
|
114
|
+
senderRole: UserRole;
|
|
115
|
+
body: string;
|
|
116
|
+
type: MessageType;
|
|
117
|
+
mediaUrl?: string;
|
|
118
|
+
/** Duration in seconds for audio messages */
|
|
119
|
+
mediaDuration?: number;
|
|
120
|
+
/** Original file name for file messages */
|
|
121
|
+
fileName?: string;
|
|
122
|
+
metadata?: Record<string, unknown>;
|
|
123
|
+
insertedAt: string;
|
|
124
|
+
readBy?: string[];
|
|
125
|
+
/** When this message is a reply, the ID of the quoted message */
|
|
126
|
+
replyToId?: string;
|
|
127
|
+
/** Frozen snapshot of the quoted message */
|
|
128
|
+
replyToSnapshot?: MessageSnapshot;
|
|
129
|
+
/** Whether this message is pinned to the top of the thread */
|
|
130
|
+
isPinned?: boolean;
|
|
131
|
+
pinnedAt?: string;
|
|
132
|
+
pinnedBy?: string;
|
|
133
|
+
}
|
|
134
|
+
interface SendMessagePayload {
|
|
135
|
+
body: string;
|
|
136
|
+
type: MessageType;
|
|
137
|
+
mediaUrl?: string;
|
|
138
|
+
mediaDuration?: number;
|
|
139
|
+
fileName?: string;
|
|
140
|
+
metadata?: Record<string, unknown>;
|
|
141
|
+
/** ID of message being replied to (snapshot is built server-side) */
|
|
142
|
+
replyToId?: string;
|
|
143
|
+
}
|
|
144
|
+
type ChannelEvent = 'message:new' | 'message:read' | 'message:pinned' | 'message:unpinned' | 'user:typing' | 'user:joined' | 'user:left' | 'channel:updated' | 'channel:deleted';
|
|
145
|
+
interface TypingEvent {
|
|
146
|
+
userId: string;
|
|
147
|
+
userName: string;
|
|
148
|
+
isTyping: boolean;
|
|
149
|
+
}
|
|
150
|
+
interface CollabError {
|
|
151
|
+
code: string;
|
|
152
|
+
message: string;
|
|
153
|
+
details?: unknown;
|
|
154
|
+
}
|
|
155
|
+
interface ChannelUpdatePayload {
|
|
156
|
+
name?: string;
|
|
157
|
+
picture?: string;
|
|
158
|
+
}
|
|
159
|
+
interface InviteUserPayload {
|
|
160
|
+
userId: string;
|
|
161
|
+
userName: string;
|
|
162
|
+
userRole: UserRole;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface CollabPanelProps {
|
|
166
|
+
/** Order ID — one conversation per study */
|
|
167
|
+
orderId: string;
|
|
168
|
+
/** Patient/study data displayed in the header */
|
|
169
|
+
patientData: PatientData;
|
|
170
|
+
/** Participant user IDs — only used when creating a brand-new conversation */
|
|
171
|
+
participantIds?: string[];
|
|
172
|
+
/** Show "seen by" indicator under own messages (default true) */
|
|
173
|
+
showSeenBy?: boolean;
|
|
174
|
+
/** Custom class name for the outer container */
|
|
175
|
+
className?: string;
|
|
176
|
+
/** Custom inline styles for the outer container */
|
|
177
|
+
style?: React.CSSProperties;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Full collaboration panel: patient header + pinned bar + message thread + input.
|
|
181
|
+
* Supports reply, pin/unpin, and (optionally) seen-by indicators.
|
|
182
|
+
*/
|
|
183
|
+
declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
|
|
184
|
+
|
|
185
|
+
interface CollabPopupProps {
|
|
186
|
+
/** Order ID for this conversation */
|
|
187
|
+
orderId: string;
|
|
188
|
+
/** Patient/study data */
|
|
189
|
+
patientData: PatientData;
|
|
190
|
+
/** Participant user IDs — only used when creating a brand-new conversation */
|
|
191
|
+
participantIds?: string[];
|
|
192
|
+
/** Whether the popup is open */
|
|
193
|
+
isOpen: boolean;
|
|
194
|
+
/** Close the popup */
|
|
195
|
+
onClose: () => void;
|
|
196
|
+
/** Initial position (defaults to bottom-right) */
|
|
197
|
+
initialPosition?: {
|
|
198
|
+
x: number;
|
|
199
|
+
y: number;
|
|
200
|
+
};
|
|
201
|
+
/** Width of the popup */
|
|
202
|
+
width?: number;
|
|
203
|
+
/** Height of the popup */
|
|
204
|
+
height?: number;
|
|
205
|
+
className?: string;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Draggable floating chat popup — drop-in replacement for DraggableChatPopup.
|
|
209
|
+
* Renders a CollabPanel inside a movable, resizable container.
|
|
210
|
+
*/
|
|
211
|
+
declare function CollabPopup({ orderId, patientData, participantIds, isOpen, onClose, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
|
|
212
|
+
|
|
213
|
+
interface CollabInlineProps {
|
|
214
|
+
orderId: string;
|
|
215
|
+
patientData: PatientData;
|
|
216
|
+
/** User IDs to invite when the first message creates the conversation */
|
|
217
|
+
participantIds?: string[];
|
|
218
|
+
/** Called when user clicks the expand button — host opens the floating popup */
|
|
219
|
+
onExpand?: () => void;
|
|
220
|
+
/** Max inline messages to show (default 5) */
|
|
221
|
+
messageLimit?: number;
|
|
222
|
+
/** Placeholder text for the input */
|
|
223
|
+
placeholder?: string;
|
|
224
|
+
className?: string;
|
|
225
|
+
style?: React.CSSProperties;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Inline chat for table rows.
|
|
229
|
+
*
|
|
230
|
+
* Two states:
|
|
231
|
+
* - No conversation yet: just an input bar (no socket, no preview)
|
|
232
|
+
* - Conversation exists: last N messages + input + expand button
|
|
233
|
+
*
|
|
234
|
+
* Supports text and voice messages. File attachments and full chat features
|
|
235
|
+
* stay in the floating popup / inbox.
|
|
236
|
+
*/
|
|
237
|
+
declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
|
|
238
|
+
|
|
239
|
+
interface CollabInboxProps {
|
|
240
|
+
/** Optional: preselect a specific conversation (by conversation ID) */
|
|
241
|
+
initialConversationId?: string;
|
|
242
|
+
/** Optional callback when the user selects a conversation */
|
|
243
|
+
onSelectConversation?: (item: ConversationListItem$1) => void;
|
|
244
|
+
/** Title for the sidebar */
|
|
245
|
+
title?: string;
|
|
246
|
+
className?: string;
|
|
247
|
+
style?: React.CSSProperties;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Full-page inbox — WhatsApp-style sidebar of all user's conversations
|
|
251
|
+
* with the selected conversation's CollabPanel on the right.
|
|
252
|
+
*
|
|
253
|
+
* Uses the same CollabPanel as the floating popup, so all features
|
|
254
|
+
* (reply, pin, seen-by, DICOM, voice, attachments) are available.
|
|
255
|
+
*/
|
|
256
|
+
declare function CollabInbox({ initialConversationId, onSelectConversation, title, className, style, }: CollabInboxProps): react_jsx_runtime.JSX.Element;
|
|
257
|
+
|
|
258
|
+
type MessageCallback = (message: Message) => void;
|
|
259
|
+
type TypingCallback = (event: TypingEvent) => void;
|
|
260
|
+
type ParticipantCallback = (participant: Participant) => void;
|
|
261
|
+
type ChannelUpdateCallback = (conversation: Partial<Conversation>) => void;
|
|
262
|
+
type PresenceCallback = (presences: Record<string, {
|
|
263
|
+
userId: string;
|
|
264
|
+
isOnline: boolean;
|
|
265
|
+
}>) => void;
|
|
266
|
+
interface ConversationCallbacks {
|
|
267
|
+
onMessage?: MessageCallback;
|
|
268
|
+
onTyping?: TypingCallback;
|
|
269
|
+
onUserJoined?: ParticipantCallback;
|
|
270
|
+
onUserLeft?: ParticipantCallback;
|
|
271
|
+
onChannelUpdated?: ChannelUpdateCallback;
|
|
272
|
+
onChannelDeleted?: () => void;
|
|
273
|
+
onPresence?: PresenceCallback;
|
|
274
|
+
onMessageRead?: (data: {
|
|
275
|
+
messageId: string;
|
|
276
|
+
userId: string;
|
|
277
|
+
}) => void;
|
|
278
|
+
onMessagePinned?: (message: Message) => void;
|
|
279
|
+
onMessageUnpinned?: (data: {
|
|
280
|
+
messageId: string;
|
|
281
|
+
}) => void;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Manages Phoenix WebSocket connection and channel subscriptions.
|
|
285
|
+
* One instance per authenticated user session.
|
|
286
|
+
*/
|
|
287
|
+
declare class CollabSocket {
|
|
288
|
+
private socket;
|
|
289
|
+
private channels;
|
|
290
|
+
private presences;
|
|
291
|
+
private userChannel;
|
|
292
|
+
private config;
|
|
293
|
+
private onUnreadUpdate;
|
|
294
|
+
/** Connect to Phoenix WebSocket server. Idempotent — safe to call when
|
|
295
|
+
* the token becomes available later (e.g. after async login) without
|
|
296
|
+
* stacking up multiple open sockets. */
|
|
297
|
+
connect(config: CollabConfig): void;
|
|
298
|
+
/** Subscribe to user-level events (unread counts, notifications) */
|
|
299
|
+
private joinUserChannel;
|
|
300
|
+
/** Register callback for unread count changes */
|
|
301
|
+
onUnreadCountUpdate(callback: (counts: Record<string, number>) => void): void;
|
|
302
|
+
/** Join a conversation channel and subscribe to events */
|
|
303
|
+
joinConversation(conversationId: string, callbacks: ConversationCallbacks): Channel | null;
|
|
304
|
+
/** Leave a conversation channel */
|
|
305
|
+
leaveConversation(conversationId: string): void;
|
|
306
|
+
/** Send a message to a conversation.
|
|
307
|
+
*
|
|
308
|
+
* Phoenix buffers pushes fired while a channel is still joining and
|
|
309
|
+
* replays them on successful join. The default per-push timeout is 10s,
|
|
310
|
+
* which is tight on slow dev backends — we bump to 30s so a late join
|
|
311
|
+
* doesn't surface as a "timed out" error to a message that ultimately
|
|
312
|
+
* persists. The caller still gets 'ok' once the server acks.
|
|
313
|
+
*/
|
|
314
|
+
sendMessage(conversationId: string, payload: SendMessagePayload): Promise<Message>;
|
|
315
|
+
/** Broadcast typing indicator */
|
|
316
|
+
sendTyping(conversationId: string, isTyping: boolean): void;
|
|
317
|
+
/** Mark messages as read */
|
|
318
|
+
markAsRead(conversationId: string, messageId: string): void;
|
|
319
|
+
/** Add a user to the conversation */
|
|
320
|
+
inviteUser(conversationId: string, payload: InviteUserPayload): Promise<Participant>;
|
|
321
|
+
/** Remove a user from the conversation */
|
|
322
|
+
removeUser(conversationId: string, userId: string): Promise<void>;
|
|
323
|
+
/** Update channel name/picture */
|
|
324
|
+
updateChannel(conversationId: string, payload: ChannelUpdatePayload): Promise<Conversation>;
|
|
325
|
+
/** Delete channel (admin only) */
|
|
326
|
+
deleteChannel(conversationId: string): Promise<void>;
|
|
327
|
+
/** Pin a message in the conversation (max MAX_PINNED_MESSAGES) */
|
|
328
|
+
pinMessage(conversationId: string, messageId: string): Promise<Message>;
|
|
329
|
+
/** Unpin a message in the conversation */
|
|
330
|
+
unpinMessage(conversationId: string, messageId: string): Promise<void>;
|
|
331
|
+
/** Generic push with promise wrapper */
|
|
332
|
+
private channelPush;
|
|
333
|
+
/** Check if connected */
|
|
334
|
+
isConnected(): boolean;
|
|
335
|
+
/** Disconnect socket and leave all channels */
|
|
336
|
+
disconnect(): void;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface FetchMessagesOptions {
|
|
340
|
+
before?: string;
|
|
341
|
+
limit?: number;
|
|
342
|
+
}
|
|
343
|
+
interface CreateConversationOptions {
|
|
344
|
+
name: string;
|
|
345
|
+
participantIds?: string[];
|
|
346
|
+
}
|
|
347
|
+
interface CollabContextValue {
|
|
348
|
+
socket: CollabSocket;
|
|
349
|
+
config: CollabConfig;
|
|
350
|
+
apiBaseUrl: string;
|
|
351
|
+
totalUnread: number;
|
|
352
|
+
/** Batched preview lookup — coalesces calls within a microtask */
|
|
353
|
+
requestPreview: (orderId: string) => Promise<ConversationPreview | null>;
|
|
354
|
+
/** Invalidate a cached preview (e.g. when a new message arrives) */
|
|
355
|
+
invalidatePreview: (orderId: string) => void;
|
|
356
|
+
/** Fetch paginated message history */
|
|
357
|
+
fetchMessages: (conversationId: string, options?: FetchMessagesOptions) => Promise<Message[]>;
|
|
358
|
+
/** Atomic create-or-send — creates conversation if missing, then stores the first message */
|
|
359
|
+
createConversationWithMessage: (orderId: string, message: SendMessagePayload, options: CreateConversationOptions) => Promise<CreateConversationResponse>;
|
|
360
|
+
/** Add a user to an existing conversation (e.g. when radiologist is assigned later) */
|
|
361
|
+
addParticipant: (conversationId: string, userId: string) => Promise<void>;
|
|
362
|
+
/** Remove a user from a conversation */
|
|
363
|
+
removeParticipant: (conversationId: string, userId: string) => Promise<void>;
|
|
364
|
+
/** Fetch the current user's full conversation list — feeds the CollabInbox sidebar */
|
|
365
|
+
fetchConversationList: () => Promise<ConversationListItem$1[]>;
|
|
366
|
+
/** Pin a message (max MAX_PINNED_MESSAGES per conversation — backend enforces) */
|
|
367
|
+
pinMessage: (conversationId: string, messageId: string) => Promise<void>;
|
|
368
|
+
/** Unpin a message */
|
|
369
|
+
unpinMessage: (conversationId: string, messageId: string) => Promise<void>;
|
|
370
|
+
/** Fetch current pinned messages for a conversation */
|
|
371
|
+
fetchPinnedMessages: (conversationId: string) => Promise<Message[]>;
|
|
372
|
+
}
|
|
373
|
+
declare function useCollab(): CollabContextValue;
|
|
374
|
+
interface CollabProviderProps {
|
|
375
|
+
config: CollabConfig;
|
|
376
|
+
children: React.ReactNode;
|
|
377
|
+
/** REST API base URL for the Phoenix backend (e.g. https://backend.natoe.ai) */
|
|
378
|
+
apiBaseUrl: string;
|
|
379
|
+
}
|
|
380
|
+
declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
|
|
381
|
+
|
|
382
|
+
interface UseConversationOptions {
|
|
383
|
+
orderId: string;
|
|
384
|
+
patientData: PatientData;
|
|
385
|
+
/** Participant user IDs used only when creating a brand-new conversation */
|
|
386
|
+
participantIds?: string[];
|
|
387
|
+
/** Load full history on mount (default true) */
|
|
388
|
+
loadHistory?: boolean;
|
|
389
|
+
}
|
|
390
|
+
interface UseConversationReturn {
|
|
391
|
+
conversation: Conversation | null;
|
|
392
|
+
messages: Message[];
|
|
393
|
+
pinnedMessages: Message[];
|
|
394
|
+
participants: Participant[];
|
|
395
|
+
typingUsers: TypingEvent[];
|
|
396
|
+
isLoading: boolean;
|
|
397
|
+
hasMore: boolean;
|
|
398
|
+
error: string | null;
|
|
399
|
+
isConnected: boolean;
|
|
400
|
+
/** Message currently being replied to (null if not replying) */
|
|
401
|
+
replyTo: Message | null;
|
|
402
|
+
/** Set/clear the active reply target */
|
|
403
|
+
setReplyTo: (message: Message | null) => void;
|
|
404
|
+
sendMessage: (body: string) => Promise<void>;
|
|
405
|
+
sendAudioMessage: (audioBlob: Blob, duration: number) => Promise<void>;
|
|
406
|
+
sendFileMessage: (file: File) => Promise<void>;
|
|
407
|
+
sendDeepLink: (path: string, label: string) => Promise<void>;
|
|
408
|
+
sendTyping: (isTyping: boolean) => void;
|
|
409
|
+
markAsRead: (messageId: string) => void;
|
|
410
|
+
loadMoreMessages: () => Promise<void>;
|
|
411
|
+
pinMessage: (messageId: string) => Promise<void>;
|
|
412
|
+
unpinMessage: (messageId: string) => Promise<void>;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Full conversation hook used by CollabPanel / CollabPopup.
|
|
416
|
+
*
|
|
417
|
+
* Lazy creation: the conversation is NOT created on mount. It's only created
|
|
418
|
+
* when the user sends the first message. If a conversation already exists for
|
|
419
|
+
* the given orderId, it's loaded and joined automatically.
|
|
420
|
+
*/
|
|
421
|
+
declare function useConversation({ orderId, patientData, participantIds, loadHistory, }: UseConversationOptions): UseConversationReturn;
|
|
422
|
+
|
|
423
|
+
interface UseConversationListOptions {
|
|
424
|
+
/**
|
|
425
|
+
* When false, skips the initial auto-fetch. The caller can still invoke
|
|
426
|
+
* `refresh()` manually (e.g. on user interaction). Defaults to true.
|
|
427
|
+
*/
|
|
428
|
+
enabled?: boolean;
|
|
429
|
+
}
|
|
430
|
+
interface UseConversationListReturn {
|
|
431
|
+
conversations: ConversationListItem$1[];
|
|
432
|
+
isLoading: boolean;
|
|
433
|
+
error: string | null;
|
|
434
|
+
refresh: () => Promise<void>;
|
|
435
|
+
/** Filter conversations by query string (matches channel name + patient name) */
|
|
436
|
+
filter: (query: string) => ConversationListItem$1[];
|
|
437
|
+
/** Total unread across all conversations */
|
|
438
|
+
totalUnread: number;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Fetches the current user's full conversation list for the inbox.
|
|
442
|
+
* Conversations are returned sorted by last activity (backend-sorted).
|
|
443
|
+
*
|
|
444
|
+
* Pass `{ enabled: false }` for lazy mode — useful when the list is behind
|
|
445
|
+
* a click (e.g. a collapsed FAB panel) and you don't want to hit the API
|
|
446
|
+
* until the user opens it.
|
|
447
|
+
*/
|
|
448
|
+
declare function useConversationList(options?: UseConversationListOptions): UseConversationListReturn;
|
|
449
|
+
|
|
450
|
+
interface UseInlineCollabOptions {
|
|
451
|
+
orderId: string;
|
|
452
|
+
patientData: PatientData;
|
|
453
|
+
/** Participant IDs passed to the backend only when creating a new conversation */
|
|
454
|
+
participantIds?: string[];
|
|
455
|
+
/** Max messages to keep in inline state (default 5) */
|
|
456
|
+
messageLimit?: number;
|
|
457
|
+
}
|
|
458
|
+
interface UseInlineCollabReturn {
|
|
459
|
+
hasConversation: boolean;
|
|
460
|
+
messages: Message[];
|
|
461
|
+
participants: Participant[];
|
|
462
|
+
unreadCount: number;
|
|
463
|
+
isLoading: boolean;
|
|
464
|
+
isSubscribed: boolean;
|
|
465
|
+
error: string | null;
|
|
466
|
+
sendMessage: (body: string) => Promise<void>;
|
|
467
|
+
/** Send a voice message — uploads the blob via config.onUploadFile first */
|
|
468
|
+
sendAudioMessage: (audioBlob: Blob, duration: number) => Promise<void>;
|
|
469
|
+
/** Attach to the DOM element that represents the row — drives lazy socket subscription */
|
|
470
|
+
containerRef: (element: HTMLElement | null) => void;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Lightweight hook for inline chat in table rows.
|
|
474
|
+
*
|
|
475
|
+
* Behavior:
|
|
476
|
+
* - On mount: fetches preview (batched) — no socket work
|
|
477
|
+
* - If conversation exists AND row becomes visible: joins Phoenix channel (real-time)
|
|
478
|
+
* - If row scrolls out of view: leaves channel (frees resources)
|
|
479
|
+
* - First sendMessage creates conversation atomically + starts real-time flow
|
|
480
|
+
* - Subsequent sends go through the socket
|
|
481
|
+
*/
|
|
482
|
+
declare function useInlineCollab({ orderId, patientData, participantIds, messageLimit, }: UseInlineCollabOptions): UseInlineCollabReturn;
|
|
483
|
+
|
|
484
|
+
interface UseMessagesOptions {
|
|
485
|
+
conversationId: string | null;
|
|
486
|
+
/** Initial page size (default 30) */
|
|
487
|
+
initialLimit?: number;
|
|
488
|
+
}
|
|
489
|
+
interface UseMessagesReturn {
|
|
490
|
+
messages: Message[];
|
|
491
|
+
isLoading: boolean;
|
|
492
|
+
hasMore: boolean;
|
|
493
|
+
error: string | null;
|
|
494
|
+
loadMore: () => Promise<void>;
|
|
495
|
+
addMessage: (message: Message) => void;
|
|
496
|
+
updateMessageReadBy: (messageId: string, userId: string) => void;
|
|
497
|
+
reset: () => void;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Standalone hook for message history management.
|
|
501
|
+
* Use this if you need message data outside of useConversation
|
|
502
|
+
* (e.g. building a completely custom UI).
|
|
503
|
+
*/
|
|
504
|
+
declare function useMessages({ conversationId, initialLimit, }: UseMessagesOptions): UseMessagesReturn;
|
|
505
|
+
|
|
506
|
+
interface UseAudioRecorderReturn {
|
|
507
|
+
/** Whether the mic is currently recording */
|
|
508
|
+
isRecording: boolean;
|
|
509
|
+
/** Recording duration in seconds (updates every second while recording) */
|
|
510
|
+
duration: number;
|
|
511
|
+
/** Start recording from the microphone */
|
|
512
|
+
start: () => Promise<void>;
|
|
513
|
+
/** Stop recording and return the audio blob */
|
|
514
|
+
stop: () => Promise<{
|
|
515
|
+
blob: Blob;
|
|
516
|
+
duration: number;
|
|
517
|
+
} | null>;
|
|
518
|
+
/** Cancel recording without producing output */
|
|
519
|
+
cancel: () => void;
|
|
520
|
+
/** Whether the browser supports audio recording */
|
|
521
|
+
isSupported: boolean;
|
|
522
|
+
/** Error message if recording fails */
|
|
523
|
+
error: string | null;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Wraps the browser MediaRecorder API for voice message recording.
|
|
527
|
+
* Returns a blob suitable for uploading via config.onUploadFile.
|
|
528
|
+
*/
|
|
529
|
+
declare function useAudioRecorder(): UseAudioRecorderReturn;
|
|
530
|
+
|
|
531
|
+
interface UseChannelSettingsOptions {
|
|
532
|
+
conversationId: string | null;
|
|
533
|
+
}
|
|
534
|
+
interface UseChannelSettingsReturn {
|
|
535
|
+
/** Invite a user to the conversation */
|
|
536
|
+
inviteUser: (payload: InviteUserPayload) => Promise<void>;
|
|
537
|
+
/** Remove a user from the conversation (admin/creator only) */
|
|
538
|
+
removeUser: (userId: string) => Promise<void>;
|
|
539
|
+
/** Current user leaves the conversation */
|
|
540
|
+
leaveChannel: () => Promise<void>;
|
|
541
|
+
/** Delete the conversation entirely (admin only) */
|
|
542
|
+
deleteChannel: () => Promise<void>;
|
|
543
|
+
/** Update channel name and/or picture */
|
|
544
|
+
updateChannel: (payload: ChannelUpdatePayload) => Promise<void>;
|
|
545
|
+
/** Whether an operation is in progress */
|
|
546
|
+
isUpdating: boolean;
|
|
547
|
+
/** Last error from any operation */
|
|
548
|
+
error: CollabError | null;
|
|
549
|
+
/** Whether current user can perform admin actions */
|
|
550
|
+
isAdmin: boolean;
|
|
551
|
+
}
|
|
552
|
+
declare function useChannelSettings({ conversationId }: UseChannelSettingsOptions): UseChannelSettingsReturn;
|
|
553
|
+
|
|
554
|
+
interface UseUnreadCountReturn {
|
|
555
|
+
/** Total unread messages across all conversations */
|
|
556
|
+
totalUnread: number;
|
|
557
|
+
/** Get unread count for a specific conversation */
|
|
558
|
+
getCountForConversation: (conversationId: string) => number;
|
|
559
|
+
/** Manually clear unread count for a conversation (e.g. when user opens it) */
|
|
560
|
+
clearForConversation: (conversationId: string) => void;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Tracks unread message counts.
|
|
564
|
+
* Counts are pushed from the server via the user channel (no polling).
|
|
565
|
+
*/
|
|
566
|
+
declare function useUnreadCount(): UseUnreadCountReturn;
|
|
567
|
+
|
|
568
|
+
interface ParsedDeepLink {
|
|
569
|
+
/** Full original link string */
|
|
570
|
+
raw: string;
|
|
571
|
+
/** The path after natoe:// (e.g. "order/4521") */
|
|
572
|
+
path: string;
|
|
573
|
+
/** The resource type (e.g. "order", "report", "dicom", "study") */
|
|
574
|
+
resource: string;
|
|
575
|
+
/** The resource ID */
|
|
576
|
+
id: string;
|
|
577
|
+
}
|
|
578
|
+
interface UseDeepLinksReturn {
|
|
579
|
+
/** Parse a deep link string into structured data */
|
|
580
|
+
parseDeepLink: (link: string) => ParsedDeepLink | null;
|
|
581
|
+
/** Handle a deep link click — calls config.onDeepLink or config.onOpenDicom */
|
|
582
|
+
handleDeepLink: (link: string) => void;
|
|
583
|
+
/** Check if a string contains deep links */
|
|
584
|
+
containsDeepLink: (text: string) => boolean;
|
|
585
|
+
/** Extract all deep links from a message body */
|
|
586
|
+
extractDeepLinks: (text: string) => ParsedDeepLink[];
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Parses and handles natoe:// deep links in messages.
|
|
590
|
+
*
|
|
591
|
+
* Supported formats:
|
|
592
|
+
* natoe://order/4521 → opens order detail
|
|
593
|
+
* natoe://report/4521 → opens report
|
|
594
|
+
* natoe://dicom/4521 → opens DICOM viewer (uses onOpenDicom callback)
|
|
595
|
+
* natoe://study/4521 → opens study detail
|
|
596
|
+
*/
|
|
597
|
+
declare function useDeepLinks(): UseDeepLinksReturn;
|
|
598
|
+
|
|
599
|
+
interface UsePinnedMessagesOptions {
|
|
600
|
+
conversationId: string | null;
|
|
601
|
+
/** Initial pinned messages from preview/initial load (avoids extra fetch) */
|
|
602
|
+
initial?: Message[];
|
|
603
|
+
}
|
|
604
|
+
interface UsePinnedMessagesReturn {
|
|
605
|
+
pinnedMessages: Message[];
|
|
606
|
+
isLoading: boolean;
|
|
607
|
+
error: string | null;
|
|
608
|
+
pin: (messageId: string) => Promise<void>;
|
|
609
|
+
unpin: (messageId: string) => Promise<void>;
|
|
610
|
+
/** Handlers to wire to socket pin/unpin events */
|
|
611
|
+
handlePinned: (message: Message) => void;
|
|
612
|
+
handleUnpinned: (messageId: string) => void;
|
|
613
|
+
refresh: () => Promise<void>;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Manages pinned messages for a conversation.
|
|
617
|
+
* Backend enforces MAX_PINNED_MESSAGES (3) — attempting to pin a 4th returns an error.
|
|
618
|
+
*/
|
|
619
|
+
declare function usePinnedMessages({ conversationId, initial, }: UsePinnedMessagesOptions): UsePinnedMessagesReturn;
|
|
620
|
+
|
|
621
|
+
interface ConversationListProps {
|
|
622
|
+
conversations: ConversationListItem$1[];
|
|
623
|
+
selectedId: string | null;
|
|
624
|
+
isLoading: boolean;
|
|
625
|
+
error: string | null;
|
|
626
|
+
onSelect: (item: ConversationListItem$1) => void;
|
|
627
|
+
/** Optional title shown above the list */
|
|
628
|
+
title?: string;
|
|
629
|
+
className?: string;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Scrollable sidebar list of conversations with search and unread filter.
|
|
633
|
+
*/
|
|
634
|
+
declare function ConversationList({ conversations, selectedId, isLoading, error, onSelect, title, className, }: ConversationListProps): react_jsx_runtime.JSX.Element;
|
|
635
|
+
|
|
636
|
+
interface ConversationListItemProps {
|
|
637
|
+
item: ConversationListItem$1;
|
|
638
|
+
isSelected: boolean;
|
|
639
|
+
onClick: () => void;
|
|
640
|
+
className?: string;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Single row in the inbox sidebar — shows patient/channel name, last message
|
|
644
|
+
* preview, timestamp, and unread badge.
|
|
645
|
+
*/
|
|
646
|
+
declare function ConversationListItem({ item, isSelected, onClick, className, }: ConversationListItemProps): react_jsx_runtime.JSX.Element;
|
|
647
|
+
|
|
648
|
+
interface PatientHeaderProps {
|
|
649
|
+
patientData: PatientData;
|
|
650
|
+
participants: Participant[];
|
|
651
|
+
onOpenDicom?: () => void;
|
|
652
|
+
onOpenSettings?: () => void;
|
|
653
|
+
className?: string;
|
|
654
|
+
}
|
|
655
|
+
declare function PatientHeader({ patientData, onOpenDicom, onOpenSettings, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
|
|
656
|
+
|
|
657
|
+
interface MessageListHandle {
|
|
658
|
+
/** Scroll to a specific message by ID (used by pin jump-to) */
|
|
659
|
+
scrollToMessage: (messageId: string) => void;
|
|
660
|
+
}
|
|
661
|
+
interface MessageListProps {
|
|
662
|
+
messages: Message[];
|
|
663
|
+
currentUserId: string;
|
|
664
|
+
participants?: Participant[];
|
|
665
|
+
showSeenBy?: boolean;
|
|
666
|
+
typingUsers: TypingEvent[];
|
|
667
|
+
hasMore: boolean;
|
|
668
|
+
isLoading: boolean;
|
|
669
|
+
onLoadMore: () => void;
|
|
670
|
+
onDeepLinkClick?: (link: string) => void;
|
|
671
|
+
onMessageVisible?: (messageId: string) => void;
|
|
672
|
+
onReply?: (message: Message) => void;
|
|
673
|
+
onPin?: (messageId: string) => void;
|
|
674
|
+
onUnpin?: (messageId: string) => void;
|
|
675
|
+
onCopy?: (text: string) => void;
|
|
676
|
+
pinDisabled?: boolean;
|
|
677
|
+
className?: string;
|
|
678
|
+
}
|
|
679
|
+
declare const MessageList: React.ForwardRefExoticComponent<MessageListProps & React.RefAttributes<MessageListHandle>>;
|
|
680
|
+
|
|
681
|
+
interface MessageBubbleProps {
|
|
682
|
+
message: Message;
|
|
683
|
+
isOwn: boolean;
|
|
684
|
+
/** Participants list — needed for SeenByIndicator */
|
|
685
|
+
participants?: Participant[];
|
|
686
|
+
/** Current user ID — needed for SeenByIndicator */
|
|
687
|
+
currentUserId?: string;
|
|
688
|
+
/** Show "seen by" indicator under own messages (default true — inline disables this) */
|
|
689
|
+
showSeenBy?: boolean;
|
|
690
|
+
onDeepLinkClick?: (link: string) => void;
|
|
691
|
+
onReply?: (message: Message) => void;
|
|
692
|
+
onPin?: (messageId: string) => void;
|
|
693
|
+
onUnpin?: (messageId: string) => void;
|
|
694
|
+
onCopy?: (text: string) => void;
|
|
695
|
+
onReplyJumpTo?: (messageId: string) => void;
|
|
696
|
+
/** True if pin limit reached — disables pin action */
|
|
697
|
+
pinDisabled?: boolean;
|
|
698
|
+
className?: string;
|
|
699
|
+
}
|
|
700
|
+
declare function MessageBubble({ message, isOwn, participants, currentUserId, showSeenBy, onDeepLinkClick, onReply, onPin, onUnpin, onCopy, onReplyJumpTo, pinDisabled, className, }: MessageBubbleProps): react_jsx_runtime.JSX.Element;
|
|
701
|
+
|
|
702
|
+
interface MessageInputProps {
|
|
703
|
+
onSendText: (body: string) => Promise<void>;
|
|
704
|
+
onSendAudio: (blob: Blob, duration: number) => Promise<void>;
|
|
705
|
+
onSendFile: (file: File) => Promise<void>;
|
|
706
|
+
onTyping: (isTyping: boolean) => void;
|
|
707
|
+
/** When set, shows a ReplyPreview above the input */
|
|
708
|
+
replyTo?: Message | null;
|
|
709
|
+
/** Called when user cancels the reply */
|
|
710
|
+
onCancelReply?: () => void;
|
|
711
|
+
disabled?: boolean;
|
|
712
|
+
placeholder?: string;
|
|
713
|
+
className?: string;
|
|
714
|
+
}
|
|
715
|
+
declare function MessageInput({ onSendText, onSendAudio, onSendFile, onTyping, replyTo, onCancelReply, disabled, placeholder, className, }: MessageInputProps): react_jsx_runtime.JSX.Element;
|
|
716
|
+
|
|
717
|
+
interface MessageActionsMenuProps {
|
|
718
|
+
message: Message;
|
|
719
|
+
onReply: (message: Message) => void;
|
|
720
|
+
onPin: (messageId: string) => void;
|
|
721
|
+
onUnpin: (messageId: string) => void;
|
|
722
|
+
onCopy?: (text: string) => void;
|
|
723
|
+
/** True when the menu should show (hover / open state managed by parent) */
|
|
724
|
+
visible: boolean;
|
|
725
|
+
/** Whether we've hit the pin limit — if true, pin is disabled */
|
|
726
|
+
pinDisabled?: boolean;
|
|
727
|
+
className?: string;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Inline action menu for a single message.
|
|
731
|
+
* Parent controls visibility (typically via hover state in MessageBubble).
|
|
732
|
+
*/
|
|
733
|
+
declare function MessageActionsMenu({ message, onReply, onPin, onUnpin, onCopy, visible, pinDisabled, className, }: MessageActionsMenuProps): react_jsx_runtime.JSX.Element | null;
|
|
734
|
+
|
|
735
|
+
interface ReplyPreviewProps {
|
|
736
|
+
/** The message being replied to */
|
|
737
|
+
message: Message;
|
|
738
|
+
/** Called when user cancels the reply */
|
|
739
|
+
onCancel: () => void;
|
|
740
|
+
className?: string;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Shown above the MessageInput when the user is composing a reply.
|
|
744
|
+
* Displays the quoted message with a cancel button.
|
|
745
|
+
*/
|
|
746
|
+
declare function ReplyPreview({ message, onCancel, className }: ReplyPreviewProps): react_jsx_runtime.JSX.Element;
|
|
747
|
+
|
|
748
|
+
interface ReplyQuoteBlockProps {
|
|
749
|
+
snapshot: MessageSnapshot;
|
|
750
|
+
/** True when rendered inside an own-message bubble (affects color contrast) */
|
|
751
|
+
inOwnBubble?: boolean;
|
|
752
|
+
/** Called when user clicks the quote — host can scroll to the original message */
|
|
753
|
+
onClick?: (messageId: string) => void;
|
|
754
|
+
className?: string;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Rendered inside a MessageBubble when the message is a reply.
|
|
758
|
+
* Shows a compact quote of the original message.
|
|
759
|
+
*/
|
|
760
|
+
declare function ReplyQuoteBlock({ snapshot, inOwnBubble, onClick, className, }: ReplyQuoteBlockProps): react_jsx_runtime.JSX.Element;
|
|
761
|
+
|
|
762
|
+
interface SeenByIndicatorProps {
|
|
763
|
+
/** User IDs who have read the message */
|
|
764
|
+
readBy: string[];
|
|
765
|
+
/** All participants in the conversation — used to resolve names */
|
|
766
|
+
participants: Participant[];
|
|
767
|
+
/** Current user ID (excluded from the display) */
|
|
768
|
+
currentUserId: string;
|
|
769
|
+
/** Message sender ID (excluded from the display) */
|
|
770
|
+
senderId: string;
|
|
771
|
+
className?: string;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* WhatsApp-style "Seen by" indicator shown under own messages.
|
|
775
|
+
* Shows up to 3 names inline; if more, shows remainder count with a tap-to-expand.
|
|
776
|
+
*
|
|
777
|
+
* Only renders when the current user is the sender (seen-by on others' messages
|
|
778
|
+
* is redundant — they obviously saw their own message).
|
|
779
|
+
*/
|
|
780
|
+
declare function SeenByIndicator({ readBy, participants, currentUserId, senderId, className, }: SeenByIndicatorProps): react_jsx_runtime.JSX.Element | null;
|
|
781
|
+
|
|
782
|
+
interface PinnedMessagesBarProps {
|
|
783
|
+
pinnedMessages: Message[];
|
|
784
|
+
/** Clicking a pinned message scrolls to it in the thread */
|
|
785
|
+
onJumpTo: (messageId: string) => void;
|
|
786
|
+
/** Called when user unpins a message */
|
|
787
|
+
onUnpin: (messageId: string) => void;
|
|
788
|
+
className?: string;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Horizontal strip at the top of the thread showing pinned messages (max 3).
|
|
792
|
+
* Cycles through them on click if more than one.
|
|
793
|
+
*/
|
|
794
|
+
declare function PinnedMessagesBar({ pinnedMessages, onJumpTo, onUnpin, className, }: PinnedMessagesBarProps): react_jsx_runtime.JSX.Element | null;
|
|
795
|
+
|
|
796
|
+
interface ChannelSettingsProps {
|
|
797
|
+
conversationId: string;
|
|
798
|
+
channelName: string;
|
|
799
|
+
channelPicture?: string;
|
|
800
|
+
participants: Participant[];
|
|
801
|
+
currentUserId: string;
|
|
802
|
+
isAdmin: boolean;
|
|
803
|
+
isUpdating: boolean;
|
|
804
|
+
error: CollabError | null;
|
|
805
|
+
onUpdateChannel: (payload: {
|
|
806
|
+
name?: string;
|
|
807
|
+
picture?: string;
|
|
808
|
+
}) => Promise<void>;
|
|
809
|
+
onInviteUser: (payload: InviteUserPayload) => Promise<void>;
|
|
810
|
+
onRemoveUser: (userId: string) => Promise<void>;
|
|
811
|
+
onLeaveChannel: () => Promise<void>;
|
|
812
|
+
onDeleteChannel: () => Promise<void>;
|
|
813
|
+
onClose: () => void;
|
|
814
|
+
className?: string;
|
|
815
|
+
}
|
|
816
|
+
declare function ChannelSettings({ channelName, participants, currentUserId, isAdmin, isUpdating, error, onUpdateChannel, onInviteUser, onRemoveUser, onLeaveChannel, onDeleteChannel, onClose, className, }: ChannelSettingsProps): react_jsx_runtime.JSX.Element;
|
|
817
|
+
|
|
818
|
+
interface ParticipantsListProps {
|
|
819
|
+
participants: Participant[];
|
|
820
|
+
currentUserId: string;
|
|
821
|
+
isAdmin: boolean;
|
|
822
|
+
onRemoveUser: (userId: string) => Promise<void>;
|
|
823
|
+
className?: string;
|
|
824
|
+
}
|
|
825
|
+
declare function ParticipantsList({ participants, currentUserId, isAdmin, onRemoveUser, className, }: ParticipantsListProps): react_jsx_runtime.JSX.Element;
|
|
826
|
+
|
|
827
|
+
/** Phoenix channel event names — must match backend channel implementation */
|
|
828
|
+
declare const EVENTS: {
|
|
829
|
+
readonly MESSAGE_NEW: "message:new";
|
|
830
|
+
readonly MESSAGE_READ: "message:read";
|
|
831
|
+
readonly MESSAGE_PINNED: "message:pinned";
|
|
832
|
+
readonly MESSAGE_UNPINNED: "message:unpinned";
|
|
833
|
+
readonly USER_TYPING: "user:typing";
|
|
834
|
+
readonly USER_JOINED: "user:joined";
|
|
835
|
+
readonly USER_LEFT: "user:left";
|
|
836
|
+
readonly CHANNEL_UPDATED: "channel:updated";
|
|
837
|
+
readonly CHANNEL_DELETED: "channel:deleted";
|
|
838
|
+
readonly PRESENCE_STATE: "presence_state";
|
|
839
|
+
readonly PRESENCE_DIFF: "presence_diff";
|
|
840
|
+
};
|
|
841
|
+
/** Maximum number of pinned messages allowed per conversation */
|
|
842
|
+
declare const MAX_PINNED_MESSAGES = 3;
|
|
843
|
+
/** Message types */
|
|
844
|
+
declare const MESSAGE_TYPES: {
|
|
845
|
+
readonly TEXT: "text";
|
|
846
|
+
readonly AUDIO: "audio";
|
|
847
|
+
readonly IMAGE: "image";
|
|
848
|
+
readonly FILE: "file";
|
|
849
|
+
readonly SYSTEM: "system";
|
|
850
|
+
readonly DEEP_LINK: "deep_link";
|
|
851
|
+
};
|
|
852
|
+
/** Deep link protocol prefix */
|
|
853
|
+
declare const DEEP_LINK_PREFIX = "natoe://";
|
|
854
|
+
/** Typing indicator debounce in ms */
|
|
855
|
+
declare const TYPING_DEBOUNCE_MS = 2000;
|
|
856
|
+
/** Messages to load per page when scrolling back */
|
|
857
|
+
declare const MESSAGES_PAGE_SIZE = 30;
|
|
858
|
+
/** Supported audio MIME type for recording */
|
|
859
|
+
declare const AUDIO_MIME_TYPE = "audio/webm;codecs=opus";
|
|
860
|
+
/** Supported image types for attachments */
|
|
861
|
+
declare const SUPPORTED_IMAGE_TYPES: string[];
|
|
862
|
+
/** Max file size in bytes (20MB) */
|
|
863
|
+
declare const MAX_FILE_SIZE: number;
|
|
864
|
+
|
|
865
|
+
export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, TYPING_DEBOUNCE_MS, type TypingEvent, type UserRole, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
|