@thrillee/aegischat 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.
@@ -0,0 +1,25 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - useFileUpload Hook
3
+ // ============================================================================
4
+
5
+ import { useState } from 'react';
6
+ import type { FileAttachment, UploadProgress } from '../types';
7
+
8
+ export interface UseFileUploadOptions {
9
+ channelId: string;
10
+ }
11
+
12
+ export interface UseFileUploadReturn {
13
+ uploadProgress: UploadProgress[];
14
+ upload: (file: File) => Promise<FileAttachment | null>;
15
+ }
16
+
17
+ export function useFileUpload(_options: UseFileUploadOptions): UseFileUploadReturn {
18
+ const [uploadProgress, setUploadProgress] = useState<UploadProgress[]>([]);
19
+
20
+ const upload = async (_file: File): Promise<FileAttachment | null> => null;
21
+
22
+ return { uploadProgress, upload };
23
+ }
24
+
25
+ export default useFileUpload;
@@ -0,0 +1,36 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - useMentions Hook
3
+ // ============================================================================
4
+
5
+ export interface UseMentionsOptions {}
6
+
7
+ export interface UseMentionsReturn {
8
+ parseMentions: (content: string) => string[];
9
+ highlightMentions: (content: string) => string;
10
+ isUserMentioned: (content: string, userId: string) => boolean;
11
+ }
12
+
13
+ export function useMentions(_options: UseMentionsOptions = {}): UseMentionsReturn {
14
+ const parseMentions = (content: string): string[] => {
15
+ const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
16
+ const mentions: string[] = [];
17
+ let match;
18
+ while ((match = mentionRegex.exec(content)) !== null) {
19
+ mentions.push(match[2]);
20
+ }
21
+ return mentions;
22
+ };
23
+
24
+ const highlightMentions = (content: string): string => {
25
+ return content.replace(/@\[([^\]]+)\]\(([^)]+)\)/g, '<span class="mention">@$1</span>');
26
+ };
27
+
28
+ const isUserMentioned = (content: string, userId: string): boolean => {
29
+ const mentions = parseMentions(content);
30
+ return mentions.includes(userId);
31
+ };
32
+
33
+ return { parseMentions, highlightMentions, isUserMentioned };
34
+ }
35
+
36
+ export default useMentions;
@@ -0,0 +1,37 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - useMessages Hook
3
+ // ============================================================================
4
+
5
+ import { useCallback, useState } from 'react';
6
+ import { messagesApi } from '../services/api';
7
+ import type { Message, MessagesResponse } from '../types';
8
+
9
+ export interface UseMessagesOptions {
10
+ channelId: string;
11
+ }
12
+
13
+ export interface UseMessagesReturn {
14
+ messages: Message[];
15
+ isLoading: boolean;
16
+ hasMore: boolean;
17
+ sendMessage: (params: { content: string; type?: string; metadata?: Record<string, unknown> }) => Promise<void>;
18
+ loadMore: () => Promise<void>;
19
+ }
20
+
21
+ export function useMessages(_options: UseMessagesOptions): UseMessagesReturn {
22
+ const [messages, setMessages] = useState<Message[]>([]);
23
+ const [isLoading, setIsLoading] = useState(false);
24
+ const [hasMore, setHasMore] = useState(true);
25
+
26
+ const sendMessage = useCallback(async (params: { content: string; type?: string; metadata?: Record<string, unknown> }) => {
27
+ // Implementation would go here
28
+ }, []);
29
+
30
+ const loadMore = useCallback(async () => {
31
+ // Implementation would go here
32
+ }, []);
33
+
34
+ return { messages, isLoading, hasMore, sendMessage, loadMore };
35
+ }
36
+
37
+ export default useMessages;
@@ -0,0 +1,28 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - useReactions Hook
3
+ // ============================================================================
4
+
5
+ import { useState } from 'react';
6
+ import type { ReactionSummary } from '../types';
7
+
8
+ export interface UseReactionsOptions {
9
+ channelId: string;
10
+ messageId: string;
11
+ }
12
+
13
+ export interface UseReactionsReturn {
14
+ reactions: ReactionSummary[];
15
+ addReaction: (emoji: string) => Promise<void>;
16
+ removeReaction: (emoji: string) => Promise<void>;
17
+ }
18
+
19
+ export function useReactions(_options: UseReactionsOptions): UseReactionsReturn {
20
+ const [reactions, setReactions] = useState<ReactionSummary[]>([]);
21
+
22
+ const addReaction = async (_emoji: string) => {};
23
+ const removeReaction = async (_emoji: string) => {};
24
+
25
+ return { reactions, addReaction, removeReaction };
26
+ }
27
+
28
+ export default useReactions;
@@ -0,0 +1,28 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - useTypingIndicator Hook
3
+ // ============================================================================
4
+
5
+ import { useCallback, useState } from 'react';
6
+ import type { TypingUser } from '../types';
7
+
8
+ export interface UseTypingIndicatorOptions {
9
+ channelId: string;
10
+ ws?: WebSocket | null;
11
+ }
12
+
13
+ export interface UseTypingIndicatorReturn {
14
+ typingUsers: TypingUser[];
15
+ startTyping: () => void;
16
+ stopTyping: () => void;
17
+ }
18
+
19
+ export function useTypingIndicator(_options: UseTypingIndicatorOptions): UseTypingIndicatorReturn {
20
+ const [typingUsers, setTypingUsers] = useState<TypingUser[]>([]);
21
+
22
+ const startTyping = useCallback(() => {}, []);
23
+ const stopTyping = useCallback(() => {}, []);
24
+
25
+ return { typingUsers, startTyping, stopTyping };
26
+ }
27
+
28
+ export default useTypingIndicator;
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - Main Entry Point
3
+ // ============================================================================
4
+
5
+ export { useChat } from './hooks/useChat';
6
+ export type { UseChatOptions, UseChatReturn } from './hooks/useChat';
7
+
8
+ export { useAutoRead } from './hooks/useAutoRead';
9
+ export type { UseAutoReadOptions, UseAutoReadReturn } from './hooks/useAutoRead';
10
+
11
+ export { useChannels } from './hooks/useChannels';
12
+ export type { UseChannelsOptions, UseChannelsReturn } from './hooks/useChannels';
13
+
14
+ export { useMessages } from './hooks/useMessages';
15
+ export type { UseMessagesOptions, UseMessagesReturn } from './hooks/useMessages';
16
+
17
+ export { useTypingIndicator } from './hooks/useTypingIndicator';
18
+ export type { UseTypingIndicatorOptions } from './hooks/useTypingIndicator';
19
+
20
+ export { useReactions } from './hooks/useReactions';
21
+ export type { UseReactionsOptions } from './hooks/useReactions';
22
+
23
+ export { useFileUpload } from './hooks/useFileUpload';
24
+ export type { UseFileUploadOptions } from './hooks/useFileUpload';
25
+
26
+ export { useMentions } from './hooks/useMentions';
27
+ export type { UseMentionsOptions } from './hooks/useMentions';
28
+
29
+ export {
30
+ chatApi,
31
+ channelsApi,
32
+ messagesApi,
33
+ reactionsApi,
34
+ filesApi,
35
+ usersApi,
36
+ configureApiClient,
37
+ } from './services/api';
38
+
39
+ export type {
40
+ AegisConfig,
41
+ ChatSession,
42
+ ChatConnectParams,
43
+ UserSummary,
44
+ UserStatus,
45
+ Channel,
46
+ ChannelListItem,
47
+ ChannelType,
48
+ Message,
49
+ MessageSummary,
50
+ MessageType,
51
+ MessageStatus,
52
+ MessageMetadata,
53
+ FileAttachment,
54
+ TypingUser,
55
+ TypingEvent,
56
+ ReactionSummary,
57
+ ReactionEvent,
58
+ UploadProgress,
59
+ WebSocketMessage,
60
+ WebSocketStatus,
61
+ ApiResponse,
62
+ ApiError,
63
+ PaginationParams,
64
+ PaginationMeta,
65
+ PaginatedResponse,
66
+ MessagesResponse,
67
+ ChannelsResponse,
68
+ } from './types';
@@ -0,0 +1,370 @@
1
+ // ============================================================================
2
+ // AegisChat React SDK - API Service
3
+ // ============================================================================
4
+
5
+ import type {
6
+ ApiResponse,
7
+ ChatSession,
8
+ ChatConnectParams,
9
+ Channel,
10
+ ChannelListItem,
11
+ Message,
12
+ MessagesResponse,
13
+ UserSummary,
14
+ ReactionSummary,
15
+ FileAttachment,
16
+ UploadUrlResponse,
17
+ } from '../types';
18
+
19
+ let baseUrl = '';
20
+ let getAccessToken: () => Promise<string> | string = () => '';
21
+ let onUnauthorized: (() => void) | undefined;
22
+
23
+ export function configureApiClient(config: {
24
+ baseUrl: string;
25
+ getAccessToken: () => Promise<string> | string;
26
+ onUnauthorized?: () => void;
27
+ }): void {
28
+ baseUrl = config.baseUrl;
29
+ getAccessToken = config.getAccessToken;
30
+ onUnauthorized = config.onUnauthorized;
31
+ }
32
+
33
+ async function fetchWithAuth<T>(
34
+ path: string,
35
+ options: RequestInit = {}
36
+ ): Promise<T> {
37
+ const token = await (typeof getAccessToken === 'function'
38
+ ? getAccessToken()
39
+ : getAccessToken);
40
+
41
+ const response = await fetch(`${baseUrl}${path}`, {
42
+ ...options,
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ Authorization: `Bearer ${token}`,
46
+ ...options.headers,
47
+ },
48
+ });
49
+
50
+ if (response.status === 401) {
51
+ onUnauthorized?.();
52
+ throw new Error('Unauthorized');
53
+ }
54
+
55
+ if (!response.ok) {
56
+ const error = await response.json().catch(() => ({}));
57
+ throw new Error(error.message || `HTTP ${response.status}`);
58
+ }
59
+
60
+ return response.json();
61
+ }
62
+
63
+ export const chatApi = {
64
+ /**
65
+ * Connect to chat session
66
+ */
67
+ async connect(
68
+ params: ChatConnectParams,
69
+ signal?: AbortSignal
70
+ ): Promise<ApiResponse<ChatSession>> {
71
+ return fetchWithAuth('/api/v1/chat/connect', {
72
+ method: 'POST',
73
+ body: JSON.stringify(params),
74
+ signal,
75
+ });
76
+ },
77
+
78
+ /**
79
+ * Refresh access token
80
+ */
81
+ async refreshToken(
82
+ refreshToken: string,
83
+ signal?: AbortSignal
84
+ ): Promise<ApiResponse<{ access_token: string; expires_in: number }>> {
85
+ return fetchWithAuth('/api/v1/chat/refresh', {
86
+ method: 'POST',
87
+ body: JSON.stringify({ refresh_token: refreshToken }),
88
+ signal,
89
+ });
90
+ },
91
+ };
92
+
93
+ export const channelsApi = {
94
+ /**
95
+ * List channels
96
+ */
97
+ async list(
98
+ options: { type?: string; limit?: number } = {},
99
+ signal?: AbortSignal
100
+ ): Promise<ApiResponse<{ channels: ChannelListItem[] }>> {
101
+ const params = new URLSearchParams();
102
+ if (options.type) params.append('type', options.type);
103
+ if (options.limit) params.append('limit', String(options.limit));
104
+ const query = params.toString() ? `?${params.toString()}` : '';
105
+ return fetchWithAuth(`/api/v1/channels${query}`, { signal });
106
+ },
107
+
108
+ /**
109
+ * Get channel by ID
110
+ */
111
+ async get(channelId: string, signal?: AbortSignal): Promise<ApiResponse<Channel>> {
112
+ return fetchWithAuth(`/api/v1/channels/${channelId}`, { signal });
113
+ },
114
+
115
+ /**
116
+ * Get or create DM channel
117
+ */
118
+ async getOrCreateDM(
119
+ userId: string,
120
+ signal?: AbortSignal
121
+ ): Promise<ApiResponse<Channel>> {
122
+ return fetchWithAuth('/api/v1/channels/dm', {
123
+ method: 'POST',
124
+ body: JSON.stringify({ user_id: userId }),
125
+ signal,
126
+ });
127
+ },
128
+
129
+ /**
130
+ * Create channel
131
+ */
132
+ async create(
133
+ data: { name: string; type?: string; description?: string; metadata?: Record<string, unknown> },
134
+ signal?: AbortSignal
135
+ ): Promise<ApiResponse<Channel>> {
136
+ return fetchWithAuth('/api/v1/channels', {
137
+ method: 'POST',
138
+ body: JSON.stringify(data),
139
+ signal,
140
+ });
141
+ },
142
+
143
+ /**
144
+ * Mark channel as read
145
+ */
146
+ async markAsRead(
147
+ channelId: string,
148
+ signal?: AbortSignal
149
+ ): Promise<ApiResponse<{ unread_count: number }>> {
150
+ return fetchWithAuth(`/api/v1/channels/${channelId}/read`, {
151
+ method: 'POST',
152
+ signal,
153
+ });
154
+ },
155
+
156
+ /**
157
+ * Get channel members
158
+ */
159
+ async getMembers(
160
+ channelId: string,
161
+ signal?: AbortSignal
162
+ ): Promise<ApiResponse<{ members: UserSummary[] }>> {
163
+ return fetchWithAuth(`/api/v1/channels/${channelId}/members`, { signal });
164
+ },
165
+
166
+ /**
167
+ * Update channel
168
+ */
169
+ async update(
170
+ channelId: string,
171
+ data: { name?: string; description?: string; metadata?: Record<string, unknown> },
172
+ signal?: AbortSignal
173
+ ): Promise<ApiResponse<Channel>> {
174
+ return fetchWithAuth(`/api/v1/channels/${channelId}`, {
175
+ method: 'PATCH',
176
+ body: JSON.stringify(data),
177
+ signal,
178
+ });
179
+ },
180
+ };
181
+
182
+ export const messagesApi = {
183
+ /**
184
+ * List messages in a channel
185
+ */
186
+ async list(
187
+ channelId: string,
188
+ options: { limit?: number; before?: string } = {},
189
+ signal?: AbortSignal
190
+ ): Promise<ApiResponse<MessagesResponse>> {
191
+ const params = new URLSearchParams();
192
+ if (options.limit) params.append('limit', String(options.limit));
193
+ if (options.before) params.append('before', options.before);
194
+ const query = params.toString() ? `?${params.toString()}` : '';
195
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages${query}`, { signal });
196
+ },
197
+
198
+ /**
199
+ * Send a message
200
+ */
201
+ async send(
202
+ channelId: string,
203
+ data: { content: string; type?: string; parent_id?: string; metadata?: Record<string, unknown>; file_ids?: string[] },
204
+ signal?: AbortSignal
205
+ ): Promise<ApiResponse<Message>> {
206
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages`, {
207
+ method: 'POST',
208
+ body: JSON.stringify(data),
209
+ signal,
210
+ });
211
+ },
212
+
213
+ /**
214
+ * Update a message
215
+ */
216
+ async update(
217
+ channelId: string,
218
+ messageId: string,
219
+ data: { content?: string; metadata?: Record<string, unknown> },
220
+ signal?: AbortSignal
221
+ ): Promise<ApiResponse<Message>> {
222
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages/${messageId}`, {
223
+ method: 'PATCH',
224
+ body: JSON.stringify(data),
225
+ signal,
226
+ });
227
+ },
228
+
229
+ /**
230
+ * Delete a message
231
+ */
232
+ async delete(
233
+ channelId: string,
234
+ messageId: string,
235
+ signal?: AbortSignal
236
+ ): Promise<ApiResponse<{ success: boolean }>> {
237
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages/${messageId}`, {
238
+ method: 'DELETE',
239
+ signal,
240
+ });
241
+ },
242
+
243
+ /**
244
+ * Mark messages as delivered
245
+ */
246
+ async markDelivered(
247
+ channelId: string,
248
+ signal?: AbortSignal
249
+ ): Promise<ApiResponse<{ success: boolean }>> {
250
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages/delivered`, {
251
+ method: 'POST',
252
+ signal,
253
+ });
254
+ },
255
+
256
+ /**
257
+ * Mark messages as read
258
+ */
259
+ async markRead(
260
+ channelId: string,
261
+ signal?: AbortSignal
262
+ ): Promise<ApiResponse<{ success: boolean }>> {
263
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages/read`, {
264
+ method: 'POST',
265
+ signal,
266
+ });
267
+ },
268
+ };
269
+
270
+ export const reactionsApi = {
271
+ /**
272
+ * Add reaction to a message
273
+ */
274
+ async add(
275
+ channelId: string,
276
+ messageId: string,
277
+ emoji: string,
278
+ signal?: AbortSignal
279
+ ): Promise<ApiResponse<{ reactions: ReactionSummary[] }>> {
280
+ return fetchWithAuth(`/api/v1/channels/${channelId}/messages/${messageId}/reactions`, {
281
+ method: 'POST',
282
+ body: JSON.stringify({ emoji }),
283
+ signal,
284
+ });
285
+ },
286
+
287
+ /**
288
+ * Remove reaction from a message
289
+ */
290
+ async remove(
291
+ channelId: string,
292
+ messageId: string,
293
+ emoji: string,
294
+ signal?: AbortSignal
295
+ ): Promise<ApiResponse<{ reactions: ReactionSummary[] }>> {
296
+ return fetchWithAuth(
297
+ `/api/v1/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`,
298
+ { method: 'DELETE', signal }
299
+ );
300
+ },
301
+ };
302
+
303
+ export const filesApi = {
304
+ /**
305
+ * Get upload URL
306
+ */
307
+ async getUploadUrl(
308
+ data: { file_name: string; file_type: string; file_size: number },
309
+ signal?: AbortSignal
310
+ ): Promise<ApiResponse<UploadUrlResponse>> {
311
+ return fetchWithAuth('/api/v1/files/upload-url', {
312
+ method: 'POST',
313
+ body: JSON.stringify(data),
314
+ signal,
315
+ });
316
+ },
317
+
318
+ /**
319
+ * Confirm file upload
320
+ */
321
+ async confirm(
322
+ fileId: string,
323
+ signal?: AbortSignal
324
+ ): Promise<ApiResponse<{ file: FileAttachment }>> {
325
+ return fetchWithAuth('/api/v1/files', {
326
+ method: 'POST',
327
+ body: JSON.stringify({ file_id: fileId }),
328
+ signal,
329
+ });
330
+ },
331
+
332
+ /**
333
+ * Get download URL
334
+ */
335
+ async getDownloadUrl(
336
+ fileId: string,
337
+ signal?: AbortSignal
338
+ ): Promise<ApiResponse<{ url: string; expires_at: string }>> {
339
+ return fetchWithAuth(`/api/v1/files/${fileId}/download`, { signal });
340
+ },
341
+ };
342
+
343
+ export const usersApi = {
344
+ /**
345
+ * Search users
346
+ */
347
+ async search(
348
+ query: string,
349
+ signal?: AbortSignal
350
+ ): Promise<ApiResponse<{ users: UserSummary[] }>> {
351
+ return fetchWithAuth(`/api/v1/users/search?q=${encodeURIComponent(query)}`, { signal });
352
+ },
353
+
354
+ /**
355
+ * Get user by ID
356
+ */
357
+ async get(userId: string, signal?: AbortSignal): Promise<ApiResponse<UserSummary>> {
358
+ return fetchWithAuth(`/api/v1/users/${userId}`, { signal });
359
+ },
360
+ };
361
+
362
+ export default {
363
+ chatApi,
364
+ channelsApi,
365
+ messagesApi,
366
+ reactionsApi,
367
+ filesApi,
368
+ usersApi,
369
+ configureApiClient,
370
+ };