@weirdfingers/boards 0.1.4

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/dist/index.mjs ADDED
@@ -0,0 +1,1096 @@
1
+ // src/auth/context.tsx
2
+ import { createContext, useContext, useEffect, useState, useCallback } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var AuthContext = createContext(null);
5
+ function AuthProvider({ provider, children }) {
6
+ const [state, setState] = useState({
7
+ user: null,
8
+ status: "loading",
9
+ signIn: async () => {
10
+ },
11
+ signOut: async () => {
12
+ },
13
+ getToken: async () => null,
14
+ refreshToken: async () => null
15
+ });
16
+ const [isInitializing, setIsInitializing] = useState(true);
17
+ const [error, setError] = useState(null);
18
+ const clearError = useCallback(() => {
19
+ setError(null);
20
+ }, []);
21
+ useEffect(() => {
22
+ let mounted = true;
23
+ let unsubscribe = null;
24
+ const initializeAuth = async () => {
25
+ try {
26
+ await provider.initialize();
27
+ if (!mounted) return;
28
+ unsubscribe = provider.onAuthStateChange((newState) => {
29
+ if (mounted) {
30
+ setState(newState);
31
+ }
32
+ });
33
+ const initialState = await provider.getAuthState();
34
+ if (mounted) {
35
+ setState(initialState);
36
+ setIsInitializing(false);
37
+ }
38
+ } catch (err) {
39
+ if (mounted) {
40
+ setError(err instanceof Error ? err : new Error("Auth initialization failed"));
41
+ setIsInitializing(false);
42
+ }
43
+ }
44
+ };
45
+ initializeAuth();
46
+ return () => {
47
+ mounted = false;
48
+ if (unsubscribe) {
49
+ unsubscribe();
50
+ }
51
+ };
52
+ }, [provider]);
53
+ useEffect(() => {
54
+ return () => {
55
+ provider.destroy();
56
+ };
57
+ }, [provider]);
58
+ const contextValue = {
59
+ ...state,
60
+ isInitializing,
61
+ error,
62
+ clearError
63
+ };
64
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: contextValue, children });
65
+ }
66
+ function useAuth() {
67
+ const context = useContext(AuthContext);
68
+ if (!context) {
69
+ throw new Error("useAuth must be used within an AuthProvider");
70
+ }
71
+ return context;
72
+ }
73
+ function useAuthOptional() {
74
+ return useContext(AuthContext);
75
+ }
76
+
77
+ // src/auth/providers/base.ts
78
+ var BaseAuthProvider = class {
79
+ constructor(config = {}) {
80
+ this.config = config;
81
+ }
82
+ /**
83
+ * Get the tenant ID from config.
84
+ */
85
+ getTenantId() {
86
+ return this.config.tenantId || "default";
87
+ }
88
+ };
89
+
90
+ // src/auth/providers/none.ts
91
+ var NoAuthProvider = class extends BaseAuthProvider {
92
+ constructor(config = {}) {
93
+ super(config);
94
+ this.listeners = [];
95
+ const nodeEnv = typeof process !== "undefined" ? process.env?.NODE_ENV : "";
96
+ const isDevelopment = nodeEnv === "development" || nodeEnv === "" || nodeEnv === "test";
97
+ if (!isDevelopment) {
98
+ const error = new Error(
99
+ "NoAuthProvider cannot be used in production environments. Please configure a proper authentication provider (JWT, Supabase, Clerk, etc.)"
100
+ );
101
+ console.error("\u{1F6A8} SECURITY ERROR:", error.message);
102
+ throw error;
103
+ }
104
+ this.config = {
105
+ defaultUserId: "dev-user",
106
+ defaultEmail: "dev@example.com",
107
+ defaultDisplayName: "Development User",
108
+ ...config
109
+ };
110
+ this.defaultUser = {
111
+ id: this.config.defaultUserId,
112
+ email: this.config.defaultEmail,
113
+ name: this.config.defaultDisplayName,
114
+ avatar: void 0,
115
+ metadata: { provider: "none" },
116
+ credits: {
117
+ balance: 1e3,
118
+ reserved: 0
119
+ }
120
+ };
121
+ this.currentState = {
122
+ user: this.defaultUser,
123
+ status: "authenticated",
124
+ // Always authenticated in no-auth mode
125
+ signIn: this.signIn.bind(this),
126
+ signOut: this.signOut.bind(this),
127
+ getToken: this.getToken.bind(this),
128
+ refreshToken: this.refreshToken.bind(this)
129
+ };
130
+ if (console.warn) {
131
+ console.warn(
132
+ "\u{1F6A8} [AUTH] NoAuthProvider is active - authentication is disabled!",
133
+ {
134
+ message: "This should ONLY be used in development environments",
135
+ environment: nodeEnv || "unknown",
136
+ provider: "none"
137
+ }
138
+ );
139
+ }
140
+ }
141
+ async initialize() {
142
+ this.updateState({ user: this.defaultUser, status: "authenticated" });
143
+ }
144
+ async getAuthState() {
145
+ return this.currentState;
146
+ }
147
+ async signIn() {
148
+ if (console.info) {
149
+ console.info("[AUTH] SignIn called in no-auth mode - no action taken", {
150
+ provider: "none",
151
+ action: "signIn",
152
+ status: "ignored"
153
+ });
154
+ }
155
+ }
156
+ async signOut() {
157
+ if (console.info) {
158
+ console.info("[AUTH] SignOut called in no-auth mode - no action taken", {
159
+ provider: "none",
160
+ action: "signOut",
161
+ status: "ignored"
162
+ });
163
+ }
164
+ }
165
+ async getToken() {
166
+ return "dev-token|no-auth-mode|always-valid";
167
+ }
168
+ async refreshToken() {
169
+ return "dev-token|no-auth-mode|always-valid";
170
+ }
171
+ async getUser() {
172
+ return this.defaultUser;
173
+ }
174
+ onAuthStateChange(callback) {
175
+ callback(this.currentState);
176
+ this.listeners.push(callback);
177
+ return () => {
178
+ const index = this.listeners.indexOf(callback);
179
+ if (index > -1) {
180
+ this.listeners.splice(index, 1);
181
+ }
182
+ };
183
+ }
184
+ async destroy() {
185
+ this.listeners = [];
186
+ }
187
+ updateState(updates) {
188
+ this.currentState = { ...this.currentState, ...updates };
189
+ this.listeners.forEach((listener) => listener(this.currentState));
190
+ }
191
+ };
192
+
193
+ // src/config/ApiConfigContext.tsx
194
+ import { createContext as createContext2, useContext as useContext2 } from "react";
195
+ import { jsx as jsx2 } from "react/jsx-runtime";
196
+ var ApiConfigContext = createContext2(null);
197
+ function ApiConfigProvider({
198
+ children,
199
+ config
200
+ }) {
201
+ return /* @__PURE__ */ jsx2(ApiConfigContext.Provider, { value: config, children });
202
+ }
203
+ function useApiConfig() {
204
+ const context = useContext2(ApiConfigContext);
205
+ if (!context) {
206
+ throw new Error("useApiConfig must be used within ApiConfigProvider");
207
+ }
208
+ return context;
209
+ }
210
+
211
+ // src/graphql/client.ts
212
+ import {
213
+ createClient,
214
+ fetchExchange,
215
+ cacheExchange,
216
+ subscriptionExchange,
217
+ makeOperation
218
+ } from "urql";
219
+ import { authExchange } from "@urql/exchange-auth";
220
+ import { createClient as createWSClient } from "graphql-ws";
221
+ function createGraphQLClient({
222
+ url,
223
+ subscriptionUrl,
224
+ auth,
225
+ tenantId
226
+ }) {
227
+ const wsClient = subscriptionUrl ? createWSClient({
228
+ url: subscriptionUrl,
229
+ connectionParams: async () => {
230
+ const token = await auth.getToken();
231
+ const headers = {};
232
+ if (token) {
233
+ headers.Authorization = `Bearer ${token}`;
234
+ }
235
+ if (tenantId) {
236
+ headers["X-Tenant"] = tenantId;
237
+ }
238
+ return headers;
239
+ }
240
+ }) : null;
241
+ return createClient({
242
+ url,
243
+ exchanges: [
244
+ cacheExchange,
245
+ authExchange(async () => {
246
+ let token = await auth.getToken();
247
+ return {
248
+ addAuthToOperation: (operation) => {
249
+ const headers = {};
250
+ if (token) {
251
+ headers.Authorization = `Bearer ${token}`;
252
+ }
253
+ if (tenantId) {
254
+ headers["X-Tenant"] = tenantId;
255
+ }
256
+ const fetchOptions = typeof operation.context.fetchOptions === "function" ? operation.context.fetchOptions() : operation.context.fetchOptions || {};
257
+ return makeOperation(operation.kind, operation, {
258
+ ...operation.context,
259
+ fetchOptions: {
260
+ ...operation.context.fetchOptions,
261
+ headers: {
262
+ ...fetchOptions.headers,
263
+ ...headers
264
+ }
265
+ }
266
+ });
267
+ },
268
+ didAuthError: (error) => {
269
+ return error.graphQLErrors.some(
270
+ (e) => e.extensions?.code === "UNAUTHENTICATED" || e.extensions?.code === "UNAUTHORIZED"
271
+ );
272
+ },
273
+ willAuthError: () => {
274
+ return false;
275
+ },
276
+ refreshAuth: async () => {
277
+ token = await auth.getToken();
278
+ }
279
+ };
280
+ }),
281
+ fetchExchange,
282
+ ...wsClient ? [
283
+ subscriptionExchange({
284
+ forwardSubscription: (operation) => ({
285
+ subscribe: (sink) => ({
286
+ unsubscribe: wsClient.subscribe(
287
+ {
288
+ query: operation.query || "",
289
+ variables: operation.variables
290
+ },
291
+ sink
292
+ )
293
+ })
294
+ })
295
+ })
296
+ ] : []
297
+ ]
298
+ });
299
+ }
300
+
301
+ // src/graphql/operations.ts
302
+ import { gql } from "urql";
303
+ var USER_FRAGMENT = gql`
304
+ fragment UserFragment on User {
305
+ id
306
+ email
307
+ displayName
308
+ avatarUrl
309
+ createdAt
310
+ }
311
+ `;
312
+ var BOARD_FRAGMENT = gql`
313
+ fragment BoardFragment on Board {
314
+ id
315
+ tenantId
316
+ ownerId
317
+ title
318
+ description
319
+ isPublic
320
+ settings
321
+ metadata
322
+ createdAt
323
+ updatedAt
324
+ generationCount
325
+ }
326
+ `;
327
+ var GENERATION_FRAGMENT = gql`
328
+ fragment GenerationFragment on Generation {
329
+ id
330
+ boardId
331
+ userId
332
+ generatorName
333
+ artifactType
334
+ status
335
+ progress
336
+ storageUrl
337
+ thumbnailUrl
338
+ inputParams
339
+ outputMetadata
340
+ errorMessage
341
+ createdAt
342
+ updatedAt
343
+ completedAt
344
+ }
345
+ `;
346
+ var GET_CURRENT_USER = gql`
347
+ ${USER_FRAGMENT}
348
+ query GetCurrentUser {
349
+ me {
350
+ ...UserFragment
351
+ }
352
+ }
353
+ `;
354
+ var GET_BOARDS = gql`
355
+ ${BOARD_FRAGMENT}
356
+ ${USER_FRAGMENT}
357
+ query GetBoards($limit: Int, $offset: Int) {
358
+ myBoards(limit: $limit, offset: $offset) {
359
+ ...BoardFragment
360
+ owner {
361
+ ...UserFragment
362
+ }
363
+ }
364
+ }
365
+ `;
366
+ var GET_BOARD = gql`
367
+ ${BOARD_FRAGMENT}
368
+ ${USER_FRAGMENT}
369
+ ${GENERATION_FRAGMENT}
370
+ query GetBoard($id: UUID!) {
371
+ board(id: $id) {
372
+ ...BoardFragment
373
+ owner {
374
+ ...UserFragment
375
+ }
376
+ members {
377
+ id
378
+ boardId
379
+ userId
380
+ role
381
+ invitedBy
382
+ joinedAt
383
+ user {
384
+ ...UserFragment
385
+ }
386
+ inviter {
387
+ ...UserFragment
388
+ }
389
+ }
390
+ generations(limit: 10) {
391
+ ...GenerationFragment
392
+ }
393
+ }
394
+ }
395
+ `;
396
+ var GET_GENERATORS = gql`
397
+ query GetGenerators($artifactType: String) {
398
+ generators(artifactType: $artifactType) {
399
+ name
400
+ description
401
+ artifactType
402
+ inputSchema
403
+ }
404
+ }
405
+ `;
406
+ var GET_GENERATIONS = gql`
407
+ ${GENERATION_FRAGMENT}
408
+ query GetGenerations($boardId: UUID, $limit: Int, $offset: Int) {
409
+ generations(boardId: $boardId, limit: $limit, offset: $offset) {
410
+ ...GenerationFragment
411
+ board {
412
+ id
413
+ title
414
+ }
415
+ user {
416
+ ...UserFragment
417
+ }
418
+ }
419
+ }
420
+ `;
421
+ var GET_GENERATION = gql`
422
+ ${GENERATION_FRAGMENT}
423
+ query GetGeneration($id: UUID!) {
424
+ generation(id: $id) {
425
+ ...GenerationFragment
426
+ board {
427
+ ...BoardFragment
428
+ }
429
+ user {
430
+ ...UserFragment
431
+ }
432
+ }
433
+ }
434
+ `;
435
+ var CREATE_BOARD = gql`
436
+ ${BOARD_FRAGMENT}
437
+ ${USER_FRAGMENT}
438
+ mutation CreateBoard($input: CreateBoardInput!) {
439
+ createBoard(input: $input) {
440
+ ...BoardFragment
441
+ owner {
442
+ ...UserFragment
443
+ }
444
+ }
445
+ }
446
+ `;
447
+ var UPDATE_BOARD = gql`
448
+ ${BOARD_FRAGMENT}
449
+ mutation UpdateBoard($id: UUID!, $input: UpdateBoardInput!) {
450
+ updateBoard(id: $id, input: $input) {
451
+ ...BoardFragment
452
+ }
453
+ }
454
+ `;
455
+ var DELETE_BOARD = gql`
456
+ mutation DeleteBoard($id: UUID!) {
457
+ deleteBoard(id: $id) {
458
+ success
459
+ }
460
+ }
461
+ `;
462
+ var ADD_BOARD_MEMBER = gql`
463
+ mutation AddBoardMember($boardId: UUID!, $email: String!, $role: BoardRole!) {
464
+ addBoardMember(boardId: $boardId, email: $email, role: $role) {
465
+ id
466
+ boardId
467
+ userId
468
+ role
469
+ invitedBy
470
+ joinedAt
471
+ user {
472
+ ...UserFragment
473
+ }
474
+ }
475
+ }
476
+ `;
477
+ var UPDATE_BOARD_MEMBER_ROLE = gql`
478
+ mutation UpdateBoardMemberRole($id: UUID!, $role: BoardRole!) {
479
+ updateBoardMemberRole(id: $id, role: $role) {
480
+ id
481
+ role
482
+ }
483
+ }
484
+ `;
485
+ var REMOVE_BOARD_MEMBER = gql`
486
+ mutation RemoveBoardMember($id: UUID!) {
487
+ removeBoardMember(id: $id) {
488
+ success
489
+ }
490
+ }
491
+ `;
492
+ var CREATE_GENERATION = gql`
493
+ ${GENERATION_FRAGMENT}
494
+ mutation CreateGeneration($input: CreateGenerationInput!) {
495
+ createGeneration(input: $input) {
496
+ ...GenerationFragment
497
+ }
498
+ }
499
+ `;
500
+ var CANCEL_GENERATION = gql`
501
+ mutation CancelGeneration($id: UUID!) {
502
+ cancelGeneration(id: $id) {
503
+ id
504
+ status
505
+ }
506
+ }
507
+ `;
508
+ var RETRY_GENERATION = gql`
509
+ ${GENERATION_FRAGMENT}
510
+ mutation RetryGeneration($id: UUID!) {
511
+ retryGeneration(id: $id) {
512
+ ...GenerationFragment
513
+ }
514
+ }
515
+ `;
516
+ var BoardRole = /* @__PURE__ */ ((BoardRole2) => {
517
+ BoardRole2["VIEWER"] = "VIEWER";
518
+ BoardRole2["EDITOR"] = "EDITOR";
519
+ BoardRole2["ADMIN"] = "ADMIN";
520
+ return BoardRole2;
521
+ })(BoardRole || {});
522
+ var GenerationStatus = /* @__PURE__ */ ((GenerationStatus2) => {
523
+ GenerationStatus2["PENDING"] = "PENDING";
524
+ GenerationStatus2["RUNNING"] = "RUNNING";
525
+ GenerationStatus2["COMPLETED"] = "COMPLETED";
526
+ GenerationStatus2["FAILED"] = "FAILED";
527
+ GenerationStatus2["CANCELLED"] = "CANCELLED";
528
+ return GenerationStatus2;
529
+ })(GenerationStatus || {});
530
+ var ArtifactType = /* @__PURE__ */ ((ArtifactType4) => {
531
+ ArtifactType4["IMAGE"] = "image";
532
+ ArtifactType4["VIDEO"] = "video";
533
+ ArtifactType4["AUDIO"] = "audio";
534
+ ArtifactType4["TEXT"] = "text";
535
+ ArtifactType4["LORA"] = "lora";
536
+ ArtifactType4["MODEL"] = "model";
537
+ return ArtifactType4;
538
+ })(ArtifactType || {});
539
+
540
+ // src/hooks/useBoards.ts
541
+ import { useCallback as useCallback2, useMemo, useState as useState2 } from "react";
542
+ import { useQuery, useMutation } from "urql";
543
+ function useBoards(options = {}) {
544
+ const { limit = 50, offset = 0 } = options;
545
+ const [searchQuery, setSearchQuery] = useState2("");
546
+ const [{ data, fetching, error }, reexecuteQuery] = useQuery({
547
+ query: GET_BOARDS,
548
+ variables: { limit, offset }
549
+ });
550
+ const [, createBoardMutation] = useMutation(CREATE_BOARD);
551
+ const [, deleteBoardMutation] = useMutation(DELETE_BOARD);
552
+ const boards = useMemo(() => data?.myBoards || [], [data?.myBoards]);
553
+ const createBoard = useCallback2(
554
+ async (input) => {
555
+ const result = await createBoardMutation({ input });
556
+ if (result.error) {
557
+ throw new Error(result.error.message);
558
+ }
559
+ if (!result.data?.createBoard) {
560
+ throw new Error("Failed to create board");
561
+ }
562
+ return result.data.createBoard;
563
+ },
564
+ [createBoardMutation]
565
+ );
566
+ const deleteBoard = useCallback2(
567
+ async (boardId) => {
568
+ const result = await deleteBoardMutation({ id: boardId });
569
+ if (result.error) {
570
+ throw new Error(result.error.message);
571
+ }
572
+ if (!result.data?.deleteBoard?.success) {
573
+ throw new Error("Failed to delete board");
574
+ }
575
+ reexecuteQuery({ requestPolicy: "network-only" });
576
+ },
577
+ [deleteBoardMutation, reexecuteQuery]
578
+ );
579
+ const searchBoards = useCallback2(
580
+ async (query) => {
581
+ setSearchQuery(query);
582
+ return new Promise((resolve) => {
583
+ setTimeout(() => {
584
+ resolve(
585
+ boards.filter(
586
+ (board) => board.title.toLowerCase().includes(query.toLowerCase()) || board.description?.toLowerCase().includes(query.toLowerCase())
587
+ )
588
+ );
589
+ }, 350);
590
+ });
591
+ },
592
+ [boards]
593
+ );
594
+ const refresh = useCallback2(async () => {
595
+ await reexecuteQuery({ requestPolicy: "network-only" });
596
+ }, [reexecuteQuery]);
597
+ return {
598
+ boards,
599
+ loading: fetching,
600
+ error: error ? new Error(error.message) : null,
601
+ createBoard,
602
+ deleteBoard,
603
+ searchBoards,
604
+ refresh,
605
+ setSearchQuery,
606
+ searchQuery
607
+ };
608
+ }
609
+
610
+ // src/hooks/useBoard.ts
611
+ import { useCallback as useCallback3, useMemo as useMemo2 } from "react";
612
+ import { useQuery as useQuery2, useMutation as useMutation2 } from "urql";
613
+ function useBoard(boardId) {
614
+ const { user } = useAuth();
615
+ const [{ data, fetching, error }, reexecuteQuery] = useQuery2({
616
+ query: GET_BOARD,
617
+ variables: { id: boardId },
618
+ pause: !boardId,
619
+ requestPolicy: "cache-and-network"
620
+ // Always fetch fresh data while showing cached data
621
+ });
622
+ const [, updateBoardMutation] = useMutation2(UPDATE_BOARD);
623
+ const [, deleteBoardMutation] = useMutation2(DELETE_BOARD);
624
+ const [, addMemberMutation] = useMutation2(ADD_BOARD_MEMBER);
625
+ const [, updateMemberRoleMutation] = useMutation2(UPDATE_BOARD_MEMBER_ROLE);
626
+ const [, removeMemberMutation] = useMutation2(REMOVE_BOARD_MEMBER);
627
+ const board = useMemo2(() => data?.board || null, [data?.board]);
628
+ const members = useMemo2(() => board?.members || [], [board?.members]);
629
+ const permissions = useMemo2(() => {
630
+ if (!board || !user) {
631
+ return {
632
+ canEdit: false,
633
+ canDelete: false,
634
+ canAddMembers: false,
635
+ canRemoveMembers: false,
636
+ canGenerate: false,
637
+ canExport: false
638
+ };
639
+ }
640
+ const isOwner = board.ownerId === user.id;
641
+ const userMember = members.find(
642
+ (member) => member.userId === user.id
643
+ );
644
+ const userRole = userMember?.role;
645
+ const isAdmin = userRole === "ADMIN" /* ADMIN */;
646
+ const isEditor = userRole === "EDITOR" /* EDITOR */ || isAdmin;
647
+ const isViewer = userRole === "VIEWER" /* VIEWER */ || isEditor;
648
+ return {
649
+ canEdit: isOwner || isAdmin || isEditor,
650
+ canDelete: isOwner,
651
+ canAddMembers: isOwner || isAdmin,
652
+ canRemoveMembers: isOwner || isAdmin,
653
+ canGenerate: isOwner || isAdmin || isEditor,
654
+ canExport: isViewer
655
+ // Even viewers can export
656
+ };
657
+ }, [board, user, members]);
658
+ const updateBoard = useCallback3(
659
+ async (updates) => {
660
+ if (!boardId) {
661
+ throw new Error("Board ID is required");
662
+ }
663
+ const result = await updateBoardMutation({
664
+ id: boardId,
665
+ input: updates
666
+ });
667
+ if (result.error) {
668
+ throw new Error(result.error.message);
669
+ }
670
+ if (!result.data?.updateBoard) {
671
+ throw new Error("Failed to update board");
672
+ }
673
+ return result.data.updateBoard;
674
+ },
675
+ [boardId, updateBoardMutation]
676
+ );
677
+ const deleteBoard = useCallback3(async () => {
678
+ if (!boardId) {
679
+ throw new Error("Board ID is required");
680
+ }
681
+ const result = await deleteBoardMutation({ id: boardId });
682
+ if (result.error) {
683
+ throw new Error(result.error.message);
684
+ }
685
+ if (!result.data?.deleteBoard?.success) {
686
+ throw new Error("Failed to delete board");
687
+ }
688
+ }, [boardId, deleteBoardMutation]);
689
+ const addMember = useCallback3(
690
+ async (email, role) => {
691
+ if (!boardId) {
692
+ throw new Error("Board ID is required");
693
+ }
694
+ const result = await addMemberMutation({
695
+ boardId,
696
+ email,
697
+ role
698
+ });
699
+ if (result.error) {
700
+ throw new Error(result.error.message);
701
+ }
702
+ if (!result.data?.addBoardMember) {
703
+ throw new Error("Failed to add member");
704
+ }
705
+ reexecuteQuery({ requestPolicy: "network-only" });
706
+ return result.data.addBoardMember;
707
+ },
708
+ [boardId, addMemberMutation, reexecuteQuery]
709
+ );
710
+ const removeMember = useCallback3(
711
+ async (memberId) => {
712
+ const result = await removeMemberMutation({ id: memberId });
713
+ if (result.error) {
714
+ throw new Error(result.error.message);
715
+ }
716
+ if (!result.data?.removeBoardMember?.success) {
717
+ throw new Error("Failed to remove member");
718
+ }
719
+ reexecuteQuery({ requestPolicy: "network-only" });
720
+ },
721
+ [removeMemberMutation, reexecuteQuery]
722
+ );
723
+ const updateMemberRole = useCallback3(
724
+ async (memberId, role) => {
725
+ const result = await updateMemberRoleMutation({
726
+ id: memberId,
727
+ role
728
+ });
729
+ if (result.error) {
730
+ throw new Error(result.error.message);
731
+ }
732
+ if (!result.data?.updateBoardMemberRole) {
733
+ throw new Error("Failed to update member role");
734
+ }
735
+ reexecuteQuery({ requestPolicy: "network-only" });
736
+ return result.data.updateBoardMemberRole;
737
+ },
738
+ [updateMemberRoleMutation, reexecuteQuery]
739
+ );
740
+ const generateShareLink = useCallback3(
741
+ async (_options) => {
742
+ throw new Error("Share links not implemented yet");
743
+ },
744
+ []
745
+ );
746
+ const revokeShareLink = useCallback3(
747
+ async (_linkId) => {
748
+ throw new Error("Share link revocation not implemented yet");
749
+ },
750
+ []
751
+ );
752
+ const refresh = useCallback3(async () => {
753
+ await reexecuteQuery({ requestPolicy: "network-only" });
754
+ }, [reexecuteQuery]);
755
+ return {
756
+ board,
757
+ members,
758
+ permissions,
759
+ loading: fetching,
760
+ error: error ? new Error(error.message) : null,
761
+ updateBoard,
762
+ deleteBoard,
763
+ refresh,
764
+ addMember,
765
+ removeMember,
766
+ updateMemberRole,
767
+ generateShareLink,
768
+ revokeShareLink
769
+ };
770
+ }
771
+
772
+ // src/hooks/useGeneration.ts
773
+ import { useCallback as useCallback4, useState as useState3, useEffect as useEffect2, useRef } from "react";
774
+ import { fetchEventSource } from "@microsoft/fetch-event-source";
775
+ import { useMutation as useMutation3 } from "urql";
776
+ function useGeneration() {
777
+ const [progress, setProgress] = useState3(null);
778
+ const [result, setResult] = useState3(null);
779
+ const [error, setError] = useState3(null);
780
+ const [isGenerating, setIsGenerating] = useState3(false);
781
+ const [history, setHistory] = useState3([]);
782
+ const { apiUrl } = useApiConfig();
783
+ const auth = useAuth();
784
+ const abortControllers = useRef(/* @__PURE__ */ new Map());
785
+ const [, createGenerationMutation] = useMutation3(CREATE_GENERATION);
786
+ const [, cancelGenerationMutation] = useMutation3(CANCEL_GENERATION);
787
+ const [, retryGenerationMutation] = useMutation3(RETRY_GENERATION);
788
+ useEffect2(() => {
789
+ return () => {
790
+ abortControllers.current.forEach((controller) => {
791
+ controller.abort();
792
+ });
793
+ abortControllers.current.clear();
794
+ };
795
+ }, []);
796
+ const connectToSSE = useCallback4(
797
+ async (jobId) => {
798
+ const existingController = abortControllers.current.get(jobId);
799
+ if (existingController) {
800
+ existingController.abort();
801
+ }
802
+ const abortController = new AbortController();
803
+ abortControllers.current.set(jobId, abortController);
804
+ const token = await auth.getToken();
805
+ const headers = {
806
+ Accept: "text/event-stream"
807
+ };
808
+ if (token) {
809
+ headers.Authorization = `Bearer ${token}`;
810
+ }
811
+ const sseUrl = `${apiUrl}/api/sse/generations/${jobId}/progress`;
812
+ console.log("SSE: Connecting to", sseUrl, "with headers:", headers);
813
+ try {
814
+ await fetchEventSource(sseUrl, {
815
+ headers,
816
+ signal: abortController.signal,
817
+ async onopen(response) {
818
+ console.log(
819
+ "SSE: Connection opened",
820
+ response.status,
821
+ response.statusText
822
+ );
823
+ if (response.ok) {
824
+ console.log("SSE: Connection successful");
825
+ } else {
826
+ console.error("SSE: Connection failed", response.status);
827
+ throw new Error(`SSE connection failed: ${response.statusText}`);
828
+ }
829
+ },
830
+ onmessage(event) {
831
+ console.log("SSE: Raw event received:", event);
832
+ if (!event.data || event.data.trim() === "") {
833
+ console.log("SSE: Skipping empty message");
834
+ return;
835
+ }
836
+ try {
837
+ const progressData = JSON.parse(event.data);
838
+ console.log("SSE: progress data received:", progressData);
839
+ setProgress(progressData);
840
+ if (progressData.status === "completed" || progressData.status === "failed" || progressData.status === "cancelled") {
841
+ setIsGenerating(false);
842
+ if (progressData.status === "completed") {
843
+ const mockResult = {
844
+ id: progressData.jobId,
845
+ jobId: progressData.jobId,
846
+ boardId: "",
847
+ // Would be filled from the original request
848
+ request: {},
849
+ artifacts: [],
850
+ credits: { cost: 0, balanceBefore: 0, balance: 0 },
851
+ performance: {
852
+ queueTime: 0,
853
+ processingTime: 0,
854
+ totalTime: 0
855
+ },
856
+ createdAt: /* @__PURE__ */ new Date()
857
+ };
858
+ setResult(mockResult);
859
+ setHistory((prev) => [...prev, mockResult]);
860
+ } else if (progressData.status === "failed") {
861
+ setError(new Error("Generation failed"));
862
+ }
863
+ abortController.abort();
864
+ abortControllers.current.delete(jobId);
865
+ }
866
+ } catch (err) {
867
+ console.error("Failed to parse SSE message:", err);
868
+ setError(new Error("Failed to parse progress update"));
869
+ setIsGenerating(false);
870
+ abortController.abort();
871
+ abortControllers.current.delete(jobId);
872
+ }
873
+ },
874
+ onerror(err) {
875
+ console.error("SSE connection error:", err);
876
+ console.error("SSE error details:", {
877
+ message: err instanceof Error ? err.message : String(err),
878
+ jobId,
879
+ url: sseUrl
880
+ });
881
+ setError(new Error("Lost connection to generation progress"));
882
+ setIsGenerating(false);
883
+ abortController.abort();
884
+ abortControllers.current.delete(jobId);
885
+ throw err;
886
+ },
887
+ openWhenHidden: true
888
+ // Keep connection open when tab is hidden
889
+ });
890
+ } catch (err) {
891
+ if (abortController.signal.aborted) {
892
+ console.log("SSE connection aborted for job:", jobId);
893
+ } else {
894
+ console.error("SSE connection failed:", err);
895
+ }
896
+ }
897
+ },
898
+ [apiUrl, auth]
899
+ );
900
+ const submit = useCallback4(
901
+ async (request) => {
902
+ setError(null);
903
+ setProgress(null);
904
+ setResult(null);
905
+ setIsGenerating(true);
906
+ const input = {
907
+ boardId: request.boardId,
908
+ generatorName: request.model,
909
+ artifactType: request.artifactType,
910
+ inputParams: {
911
+ ...request.inputs,
912
+ ...request.options
913
+ }
914
+ };
915
+ let lastError = null;
916
+ const maxRetries = 2;
917
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
918
+ try {
919
+ const result2 = await createGenerationMutation({ input });
920
+ if (result2.error) {
921
+ throw new Error(result2.error.message);
922
+ }
923
+ if (!result2.data?.createGeneration) {
924
+ throw new Error("Failed to create generation");
925
+ }
926
+ const jobId = result2.data.createGeneration.id;
927
+ connectToSSE(jobId);
928
+ setIsGenerating(false);
929
+ return jobId;
930
+ } catch (err) {
931
+ lastError = err instanceof Error ? err : new Error("Failed to submit generation");
932
+ if (lastError.message.includes("insufficient credits") || lastError.message.includes("validation") || lastError.message.includes("unauthorized") || lastError.message.includes("forbidden")) {
933
+ setError(lastError);
934
+ setIsGenerating(false);
935
+ throw lastError;
936
+ }
937
+ if (attempt === maxRetries) {
938
+ setError(lastError);
939
+ setIsGenerating(false);
940
+ throw lastError;
941
+ }
942
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * attempt));
943
+ }
944
+ }
945
+ const finalError = lastError || new Error("Failed to submit generation after retries");
946
+ setError(finalError);
947
+ setIsGenerating(false);
948
+ throw finalError;
949
+ },
950
+ [createGenerationMutation, connectToSSE]
951
+ );
952
+ const cancel = useCallback4(
953
+ async (jobId) => {
954
+ try {
955
+ const result2 = await cancelGenerationMutation({ id: jobId });
956
+ if (result2.error) {
957
+ throw new Error(result2.error.message);
958
+ }
959
+ const controller = abortControllers.current.get(jobId);
960
+ if (controller) {
961
+ controller.abort();
962
+ abortControllers.current.delete(jobId);
963
+ }
964
+ setIsGenerating(false);
965
+ setProgress((prev) => prev ? { ...prev, status: "cancelled" } : null);
966
+ } catch (err) {
967
+ setError(
968
+ err instanceof Error ? err : new Error("Failed to cancel generation")
969
+ );
970
+ }
971
+ },
972
+ [cancelGenerationMutation]
973
+ );
974
+ const retry = useCallback4(
975
+ async (jobId) => {
976
+ try {
977
+ setError(null);
978
+ setIsGenerating(true);
979
+ const result2 = await retryGenerationMutation({ id: jobId });
980
+ if (result2.error) {
981
+ throw new Error(result2.error.message);
982
+ }
983
+ if (!result2.data?.retryGeneration) {
984
+ throw new Error("Failed to retry generation");
985
+ }
986
+ const newJobId = result2.data.retryGeneration.id;
987
+ connectToSSE(newJobId);
988
+ } catch (err) {
989
+ setError(
990
+ err instanceof Error ? err : new Error("Failed to retry generation")
991
+ );
992
+ setIsGenerating(false);
993
+ }
994
+ },
995
+ [retryGenerationMutation, connectToSSE]
996
+ );
997
+ const clearHistory = useCallback4(() => {
998
+ setHistory([]);
999
+ }, []);
1000
+ return {
1001
+ progress,
1002
+ result,
1003
+ error,
1004
+ isGenerating,
1005
+ submit,
1006
+ cancel,
1007
+ retry,
1008
+ history,
1009
+ clearHistory
1010
+ };
1011
+ }
1012
+
1013
+ // src/hooks/useGenerators.ts
1014
+ import { useMemo as useMemo3 } from "react";
1015
+ import { useQuery as useQuery3 } from "urql";
1016
+ function useGenerators(options = {}) {
1017
+ const { artifactType } = options;
1018
+ const [{ data, fetching, error }] = useQuery3({
1019
+ query: GET_GENERATORS,
1020
+ variables: artifactType ? { artifactType } : {}
1021
+ });
1022
+ const generators = useMemo3(() => data?.generators || [], [data?.generators]);
1023
+ return {
1024
+ generators,
1025
+ loading: fetching,
1026
+ error: error ? new Error(error.message) : null
1027
+ };
1028
+ }
1029
+
1030
+ // src/providers/BoardsProvider.tsx
1031
+ import { Provider as UrqlProvider } from "urql";
1032
+ import { jsx as jsx3 } from "react/jsx-runtime";
1033
+ function BoardsProvider({
1034
+ children,
1035
+ apiUrl,
1036
+ graphqlUrl,
1037
+ subscriptionUrl,
1038
+ authProvider,
1039
+ tenantId
1040
+ }) {
1041
+ const resolvedGraphqlUrl = graphqlUrl || `${apiUrl}/graphql`;
1042
+ const apiConfig = {
1043
+ apiUrl,
1044
+ graphqlUrl: resolvedGraphqlUrl,
1045
+ subscriptionUrl
1046
+ };
1047
+ const client = createGraphQLClient({
1048
+ url: resolvedGraphqlUrl,
1049
+ subscriptionUrl,
1050
+ auth: {
1051
+ getToken: () => authProvider.getAuthState().then((state) => state.getToken())
1052
+ },
1053
+ tenantId
1054
+ });
1055
+ return /* @__PURE__ */ jsx3(AuthProvider, { provider: authProvider, children: /* @__PURE__ */ jsx3(ApiConfigProvider, { config: apiConfig, children: /* @__PURE__ */ jsx3(UrqlProvider, { value: client, children }) }) });
1056
+ }
1057
+
1058
+ // src/index.ts
1059
+ var VERSION = "0.1.0";
1060
+ export {
1061
+ ADD_BOARD_MEMBER,
1062
+ ArtifactType,
1063
+ AuthProvider,
1064
+ BOARD_FRAGMENT,
1065
+ BaseAuthProvider,
1066
+ BoardRole,
1067
+ BoardsProvider,
1068
+ CANCEL_GENERATION,
1069
+ CREATE_BOARD,
1070
+ CREATE_GENERATION,
1071
+ DELETE_BOARD,
1072
+ GENERATION_FRAGMENT,
1073
+ GET_BOARD,
1074
+ GET_BOARDS,
1075
+ GET_CURRENT_USER,
1076
+ GET_GENERATION,
1077
+ GET_GENERATIONS,
1078
+ GET_GENERATORS,
1079
+ GenerationStatus,
1080
+ NoAuthProvider,
1081
+ REMOVE_BOARD_MEMBER,
1082
+ RETRY_GENERATION,
1083
+ UPDATE_BOARD,
1084
+ UPDATE_BOARD_MEMBER_ROLE,
1085
+ USER_FRAGMENT,
1086
+ VERSION,
1087
+ createGraphQLClient,
1088
+ useApiConfig,
1089
+ useAuth,
1090
+ useAuthOptional,
1091
+ useBoard,
1092
+ useBoards,
1093
+ useGeneration,
1094
+ useGenerators
1095
+ };
1096
+ //# sourceMappingURL=index.mjs.map