@weirdfingers/boards 0.2.1 → 0.4.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 ADDED
@@ -0,0 +1,678 @@
1
+ # @weirdfingers/boards
2
+
3
+ React hooks and components for building AI-powered creative applications with the Boards toolkit.
4
+
5
+ ## Overview
6
+
7
+ `@weirdfingers/boards` provides a React-first interface to the Boards creative toolkit. Build applications for AI-generated content (images, video, audio, text) using simple, composable hooks.
8
+
9
+ **Key Features:**
10
+
11
+ - 🎣 **Hooks-first design** - Clean, React-native API without opinionated UI
12
+ - 🔐 **Pluggable auth** - Works with Supabase, Clerk, Auth0, or custom providers
13
+ - 📡 **Real-time updates** - SSE for generation progress, GraphQL subscriptions for live data
14
+ - 🎨 **Multi-modal support** - Images, video, audio, text generation
15
+ - 📦 **Framework agnostic** - Works with Next.js, Remix, Vite, or any React framework
16
+ - 🔒 **Type-safe** - Full TypeScript support with generated GraphQL types
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @weirdfingers/boards react react-dom
22
+ # or
23
+ pnpm add @weirdfingers/boards react react-dom
24
+ # or
25
+ yarn add @weirdfingers/boards react react-dom
26
+ ```
27
+
28
+ **Peer Dependencies:**
29
+
30
+ - `react` ^18.0.0
31
+ - `react-dom` ^18.0.0
32
+
33
+ ## Quick Start
34
+
35
+ ### 1. Set up the provider
36
+
37
+ Wrap your application with `BoardsProvider` to configure the GraphQL client and auth:
38
+
39
+ ```tsx
40
+ import { BoardsProvider, NoAuthProvider } from '@weirdfingers/boards';
41
+
42
+ function App() {
43
+ const authProvider = new NoAuthProvider(); // For development only
44
+
45
+ return (
46
+ <BoardsProvider
47
+ apiUrl="http://localhost:8088"
48
+ authProvider={authProvider}
49
+ >
50
+ <YourApp />
51
+ </BoardsProvider>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### 2. Use hooks in your components
57
+
58
+ ```tsx
59
+ import { useBoards, useGeneration } from '@weirdfingers/boards';
60
+
61
+ function BoardsPage() {
62
+ const { boards, loading, createBoard } = useBoards();
63
+ const { submit, progress, isGenerating } = useGeneration();
64
+
65
+ const handleGenerate = async (boardId: string) => {
66
+ await submit({
67
+ boardId,
68
+ model: 'flux-1-schnell',
69
+ artifactType: 'IMAGE',
70
+ inputs: {
71
+ prompt: 'A serene mountain landscape at sunset',
72
+ },
73
+ });
74
+ };
75
+
76
+ if (loading) return <div>Loading boards...</div>;
77
+
78
+ return (
79
+ <div>
80
+ {boards.map((board) => (
81
+ <div key={board.id}>
82
+ <h2>{board.title}</h2>
83
+ <button onClick={() => handleGenerate(board.id)}>
84
+ Generate Image
85
+ </button>
86
+ </div>
87
+ ))}
88
+
89
+ {progress && (
90
+ <div>Progress: {progress.progress}%</div>
91
+ )}
92
+ </div>
93
+ );
94
+ }
95
+ ```
96
+
97
+ ## API Reference
98
+
99
+ ### Provider
100
+
101
+ #### `<BoardsProvider>`
102
+
103
+ Main provider component that sets up GraphQL client, auth, and configuration.
104
+
105
+ **Props:**
106
+
107
+ | Prop | Type | Required | Description |
108
+ |------|------|----------|-------------|
109
+ | `apiUrl` | `string` | Yes | Base URL for the backend API (e.g., `http://localhost:8088`) |
110
+ | `authProvider` | `BaseAuthProvider` | Yes | Authentication provider instance |
111
+ | `graphqlUrl` | `string` | No | GraphQL endpoint URL (defaults to `${apiUrl}/graphql`) |
112
+ | `subscriptionUrl` | `string` | No | WebSocket URL for GraphQL subscriptions |
113
+ | `tenantId` | `string` | No | Tenant ID for multi-tenant deployments |
114
+
115
+ ```tsx
116
+ <BoardsProvider
117
+ apiUrl="https://api.example.com"
118
+ authProvider={authProvider}
119
+ tenantId="my-tenant"
120
+ >
121
+ {children}
122
+ </BoardsProvider>
123
+ ```
124
+
125
+ ### Hooks
126
+
127
+ #### `useBoards(options?)`
128
+
129
+ Manage multiple boards - fetch, create, delete, and search.
130
+
131
+ **Options:**
132
+
133
+ ```typescript
134
+ interface UseBoardsOptions {
135
+ limit?: number; // Default: 50
136
+ offset?: number; // Default: 0
137
+ }
138
+ ```
139
+
140
+ **Returns:**
141
+
142
+ ```typescript
143
+ interface BoardsHook {
144
+ boards: Board[];
145
+ loading: boolean;
146
+ error: Error | null;
147
+
148
+ // Operations
149
+ createBoard: (data: CreateBoardInput) => Promise<Board>;
150
+ deleteBoard: (boardId: string) => Promise<void>;
151
+ searchBoards: (query: string) => Promise<Board[]>;
152
+ refresh: () => Promise<void>;
153
+
154
+ // Search state
155
+ setSearchQuery: (query: string) => void;
156
+ searchQuery: string;
157
+ }
158
+ ```
159
+
160
+ **Example:**
161
+
162
+ ```tsx
163
+ function BoardsList() {
164
+ const { boards, loading, createBoard, deleteBoard } = useBoards({ limit: 20 });
165
+
166
+ const handleCreate = async () => {
167
+ await createBoard({
168
+ title: 'My New Board',
169
+ description: 'A board for my creative projects',
170
+ });
171
+ };
172
+
173
+ return (
174
+ <div>
175
+ <button onClick={handleCreate}>Create Board</button>
176
+ {boards.map((board) => (
177
+ <div key={board.id}>
178
+ <h3>{board.title}</h3>
179
+ <button onClick={() => deleteBoard(board.id)}>Delete</button>
180
+ </div>
181
+ ))}
182
+ </div>
183
+ );
184
+ }
185
+ ```
186
+
187
+ #### `useBoard(boardId)`
188
+
189
+ Manage a single board - update, delete, and handle members/permissions.
190
+
191
+ **Returns:**
192
+
193
+ ```typescript
194
+ interface BoardHook {
195
+ board: Board | null;
196
+ members: BoardMember[];
197
+ permissions: BoardPermissions;
198
+ loading: boolean;
199
+ error: Error | null;
200
+
201
+ // Board operations
202
+ updateBoard: (updates: Partial<UpdateBoardInput>) => Promise<Board>;
203
+ deleteBoard: () => Promise<void>;
204
+ refresh: () => Promise<void>;
205
+
206
+ // Member management
207
+ addMember: (email: string, role: MemberRole) => Promise<BoardMember>;
208
+ removeMember: (memberId: string) => Promise<void>;
209
+ updateMemberRole: (memberId: string, role: MemberRole) => Promise<BoardMember>;
210
+
211
+ // Sharing (placeholder - backend implementation pending)
212
+ generateShareLink: (options: ShareLinkOptions) => Promise<ShareLink>;
213
+ revokeShareLink: (linkId: string) => Promise<void>;
214
+ }
215
+ ```
216
+
217
+ **Example:**
218
+
219
+ ```tsx
220
+ function BoardDetail({ boardId }: { boardId: string }) {
221
+ const {
222
+ board,
223
+ permissions,
224
+ updateBoard,
225
+ addMember,
226
+ } = useBoard(boardId);
227
+
228
+ const handleUpdate = async () => {
229
+ await updateBoard({
230
+ title: 'Updated Title',
231
+ description: 'Updated description',
232
+ });
233
+ };
234
+
235
+ const handleAddMember = async (email: string) => {
236
+ await addMember(email, 'EDITOR');
237
+ };
238
+
239
+ return (
240
+ <div>
241
+ <h1>{board?.title}</h1>
242
+ {permissions.canEdit && (
243
+ <button onClick={handleUpdate}>Update Board</button>
244
+ )}
245
+ {permissions.canAddMembers && (
246
+ <button onClick={() => handleAddMember('user@example.com')}>
247
+ Add Member
248
+ </button>
249
+ )}
250
+ </div>
251
+ );
252
+ }
253
+ ```
254
+
255
+ #### `useGeneration()`
256
+
257
+ Submit AI generation requests and track progress in real-time via Server-Sent Events (SSE).
258
+
259
+ **Returns:**
260
+
261
+ ```typescript
262
+ interface GenerationHook {
263
+ // Current generation state
264
+ progress: GenerationProgress | null;
265
+ result: GenerationResult | null;
266
+ error: Error | null;
267
+ isGenerating: boolean;
268
+
269
+ // Operations
270
+ submit: (request: GenerationRequest) => Promise<string>;
271
+ cancel: (jobId: string) => Promise<void>;
272
+ retry: (jobId: string) => Promise<void>;
273
+
274
+ // History
275
+ history: GenerationResult[];
276
+ clearHistory: () => void;
277
+ }
278
+ ```
279
+
280
+ **Example:**
281
+
282
+ ```tsx
283
+ function ImageGenerator({ boardId }: { boardId: string }) {
284
+ const { submit, progress, result, isGenerating, cancel } = useGeneration();
285
+ const [prompt, setPrompt] = useState('');
286
+
287
+ const handleGenerate = async () => {
288
+ const jobId = await submit({
289
+ boardId,
290
+ model: 'flux-1-schnell',
291
+ artifactType: 'IMAGE',
292
+ inputs: {
293
+ prompt,
294
+ steps: 4,
295
+ guidance: 3.5,
296
+ },
297
+ options: {
298
+ priority: 'normal',
299
+ },
300
+ });
301
+ console.log('Generation started:', jobId);
302
+ };
303
+
304
+ return (
305
+ <div>
306
+ <input
307
+ value={prompt}
308
+ onChange={(e) => setPrompt(e.target.value)}
309
+ placeholder="Enter your prompt..."
310
+ />
311
+ <button onClick={handleGenerate} disabled={isGenerating}>
312
+ Generate
313
+ </button>
314
+
315
+ {progress && (
316
+ <div>
317
+ <p>Status: {progress.status}</p>
318
+ <p>Progress: {progress.progress}%</p>
319
+ {progress.message && <p>{progress.message}</p>}
320
+ {isGenerating && <button onClick={() => cancel(progress.jobId)}>Cancel</button>}
321
+ </div>
322
+ )}
323
+
324
+ {result && (
325
+ <div>
326
+ <h3>Generation Complete!</h3>
327
+ {result.artifacts.map((artifact) => (
328
+ <img key={artifact.id} src={artifact.url} alt="Generated" />
329
+ ))}
330
+ </div>
331
+ )}
332
+ </div>
333
+ );
334
+ }
335
+ ```
336
+
337
+ #### `useGenerators()`
338
+
339
+ Fetch available AI generators and their input schemas.
340
+
341
+ **Returns:**
342
+
343
+ ```typescript
344
+ interface GeneratorsHook {
345
+ generators: Generator[];
346
+ loading: boolean;
347
+ error: Error | null;
348
+
349
+ // Utilities
350
+ getGenerator: (name: string) => Generator | undefined;
351
+ refresh: () => Promise<void>;
352
+ }
353
+
354
+ interface Generator {
355
+ name: string;
356
+ displayName: string;
357
+ description: string;
358
+ artifactType: ArtifactType;
359
+ version: string;
360
+ inputSchema: JSONSchema7; // JSON Schema for inputs
361
+ enabled: boolean;
362
+ creditCost: number;
363
+ estimatedDuration: number;
364
+ maxDuration: number;
365
+ metadata: Record<string, unknown>;
366
+ }
367
+ ```
368
+
369
+ **Example:**
370
+
371
+ ```tsx
372
+ function GeneratorSelector() {
373
+ const { generators, loading, getGenerator } = useGenerators();
374
+
375
+ const fluxGenerator = getGenerator('flux-1-schnell');
376
+
377
+ return (
378
+ <div>
379
+ <h2>Available Generators</h2>
380
+ {generators
381
+ .filter((g) => g.enabled)
382
+ .map((generator) => (
383
+ <div key={generator.name}>
384
+ <h3>{generator.displayName}</h3>
385
+ <p>{generator.description}</p>
386
+ <p>Cost: {generator.creditCost} credits</p>
387
+ <p>Type: {generator.artifactType}</p>
388
+ </div>
389
+ ))}
390
+ </div>
391
+ );
392
+ }
393
+ ```
394
+
395
+ #### `useAuth()`
396
+
397
+ Access authentication state and user information.
398
+
399
+ **Returns:**
400
+
401
+ ```typescript
402
+ interface UseAuthReturn {
403
+ user: User | null;
404
+ loading: boolean;
405
+ error: Error | null;
406
+
407
+ // Auth operations
408
+ getToken: () => Promise<string | null>;
409
+ signOut: () => Promise<void>;
410
+ }
411
+
412
+ interface User {
413
+ id: string;
414
+ email: string;
415
+ displayName: string;
416
+ avatarUrl?: string;
417
+ createdAt: string;
418
+ }
419
+ ```
420
+
421
+ **Example:**
422
+
423
+ ```tsx
424
+ function UserProfile() {
425
+ const { user, loading, signOut } = useAuth();
426
+
427
+ if (loading) return <div>Loading...</div>;
428
+ if (!user) return <div>Not authenticated</div>;
429
+
430
+ return (
431
+ <div>
432
+ <h2>Welcome, {user.displayName}</h2>
433
+ <p>{user.email}</p>
434
+ <button onClick={signOut}>Sign Out</button>
435
+ </div>
436
+ );
437
+ }
438
+ ```
439
+
440
+ ## Authentication
441
+
442
+ The package supports multiple auth providers through a plugin system.
443
+
444
+ ### Development (No Auth)
445
+
446
+ For local development and testing:
447
+
448
+ ```tsx
449
+ import { NoAuthProvider } from '@weirdfingers/boards';
450
+
451
+ const authProvider = new NoAuthProvider();
452
+ ```
453
+
454
+ ### Custom Auth Provider
455
+
456
+ Implement `BaseAuthProvider` for custom authentication:
457
+
458
+ ```tsx
459
+ import { BaseAuthProvider, AuthState } from '@weirdfingers/boards';
460
+
461
+ class CustomAuthProvider extends BaseAuthProvider {
462
+ async initialize(): Promise<void> {
463
+ // Initialize your auth system
464
+ }
465
+
466
+ async getAuthState(): Promise<AuthState> {
467
+ // Return current auth state
468
+ return {
469
+ isAuthenticated: true,
470
+ userId: 'user-id',
471
+ getToken: async () => 'your-jwt-token',
472
+ };
473
+ }
474
+
475
+ async signOut(): Promise<void> {
476
+ // Handle sign out
477
+ }
478
+ }
479
+ ```
480
+
481
+ ### Auth Adapter Packages (Coming Soon)
482
+
483
+ Dedicated packages for popular auth providers:
484
+
485
+ - `@weirdfingers/boards-auth-supabase` - Supabase authentication
486
+ - `@weirdfingers/boards-auth-clerk` - Clerk authentication
487
+ - `@weirdfingers/boards-auth-auth0` - Auth0 authentication
488
+
489
+ ## TypeScript Support
490
+
491
+ This package is written in TypeScript and includes full type definitions. All hooks and components are fully typed:
492
+
493
+ ```tsx
494
+ import type {
495
+ Board,
496
+ Generator,
497
+ GenerationRequest,
498
+ GenerationProgress,
499
+ BoardRole,
500
+ ArtifactType,
501
+ } from '@weirdfingers/boards';
502
+
503
+ // Types are automatically inferred
504
+ const { boards } = useBoards(); // boards: Board[]
505
+ ```
506
+
507
+ ## Examples
508
+
509
+ ### Complete Application Setup
510
+
511
+ ```tsx
512
+ // App.tsx
513
+ import { BoardsProvider, NoAuthProvider } from '@weirdfingers/boards';
514
+ import { BoardsPage } from './BoardsPage';
515
+
516
+ const authProvider = new NoAuthProvider();
517
+
518
+ export function App() {
519
+ return (
520
+ <BoardsProvider
521
+ apiUrl={import.meta.env.VITE_API_URL || 'http://localhost:8088'}
522
+ authProvider={authProvider}
523
+ >
524
+ <BoardsPage />
525
+ </BoardsProvider>
526
+ );
527
+ }
528
+
529
+ // BoardsPage.tsx
530
+ import { useBoards, useGeneration } from '@weirdfingers/boards';
531
+
532
+ export function BoardsPage() {
533
+ const { boards, loading, createBoard } = useBoards();
534
+ const { submit, progress, isGenerating } = useGeneration();
535
+
536
+ const handleCreateBoard = async () => {
537
+ const board = await createBoard({
538
+ title: 'My Creative Board',
539
+ description: 'A space for AI-generated art',
540
+ });
541
+ console.log('Created board:', board.id);
542
+ };
543
+
544
+ const handleGenerate = async (boardId: string) => {
545
+ await submit({
546
+ boardId,
547
+ model: 'flux-1-schnell',
548
+ artifactType: 'IMAGE',
549
+ inputs: {
550
+ prompt: 'A futuristic cityscape at night',
551
+ steps: 4,
552
+ guidance: 3.5,
553
+ },
554
+ });
555
+ };
556
+
557
+ if (loading) return <div>Loading...</div>;
558
+
559
+ return (
560
+ <div>
561
+ <h1>My Boards</h1>
562
+ <button onClick={handleCreateBoard}>Create New Board</button>
563
+
564
+ <div className="boards-grid">
565
+ {boards.map((board) => (
566
+ <div key={board.id}>
567
+ <h2>{board.title}</h2>
568
+ <p>{board.description}</p>
569
+ <p>Generations: {board.generationCount}</p>
570
+ <button onClick={() => handleGenerate(board.id)}>
571
+ Generate Image
572
+ </button>
573
+ </div>
574
+ ))}
575
+ </div>
576
+
577
+ {progress && (
578
+ <div className="progress-bar">
579
+ <p>Generating: {progress.progress}%</p>
580
+ <p>{progress.message}</p>
581
+ </div>
582
+ )}
583
+ </div>
584
+ );
585
+ }
586
+ ```
587
+
588
+ ### Handling Real-time Progress
589
+
590
+ ```tsx
591
+ import { useGeneration } from '@weirdfingers/boards';
592
+ import { useEffect, useState } from 'react';
593
+
594
+ function ProgressTracker() {
595
+ const { submit, progress, result, error } = useGeneration();
596
+ const [logs, setLogs] = useState<string[]>([]);
597
+
598
+ useEffect(() => {
599
+ if (progress?.message) {
600
+ setLogs((prev) => [...prev, `${progress.status}: ${progress.message}`]);
601
+ }
602
+ }, [progress]);
603
+
604
+ const handleGenerate = async () => {
605
+ setLogs([]);
606
+ await submit({
607
+ boardId: 'board-id',
608
+ model: 'flux-1-schnell',
609
+ artifactType: 'IMAGE',
610
+ inputs: { prompt: 'A magical forest' },
611
+ });
612
+ };
613
+
614
+ return (
615
+ <div>
616
+ <button onClick={handleGenerate}>Generate</button>
617
+
618
+ <div className="logs">
619
+ {logs.map((log, i) => (
620
+ <div key={i}>{log}</div>
621
+ ))}
622
+ </div>
623
+
624
+ {result && (
625
+ <div>
626
+ <h3>Complete!</h3>
627
+ <p>Time: {result.performance.totalTime}ms</p>
628
+ <p>Cost: {result.credits.cost} credits</p>
629
+ </div>
630
+ )}
631
+
632
+ {error && <div className="error">{error.message}</div>}
633
+ </div>
634
+ );
635
+ }
636
+ ```
637
+
638
+ ## Related Packages
639
+
640
+ - **Backend**: [`@weirdfingers/boards-backend`](https://pypi.org/project/boards/) - Python backend package (GraphQL API, database models, job workers)
641
+ - **CLI**: [`@weirdfingers/boards-cli`](https://www.npmjs.com/package/@weirdfingers/boards-cli) - CLI for scaffolding and deployment
642
+
643
+ ## Documentation
644
+
645
+ For comprehensive documentation, guides, and tutorials, visit:
646
+
647
+ **[docs.weirdfingers.com](https://docs.weirdfingers.com)**
648
+
649
+ ## Contributing
650
+
651
+ Contributions are welcome! Please see the [main repository](https://github.com/weirdfingers/boards) for:
652
+
653
+ - [Contributing Guidelines](https://github.com/weirdfingers/boards/blob/main/CONTRIBUTING.md)
654
+ - [Code of Conduct](https://github.com/weirdfingers/boards/blob/main/CODE_OF_CONDUCT.md)
655
+ - [Development Setup](https://github.com/weirdfingers/boards#development)
656
+
657
+ ## License
658
+
659
+ MIT License - see [LICENSE](https://github.com/weirdfingers/boards/blob/main/LICENSE) for details.
660
+
661
+ ## Support
662
+
663
+ - **GitHub Issues**: [github.com/weirdfingers/boards/issues](https://github.com/weirdfingers/boards/issues)
664
+ - **Documentation**: [docs.weirdfingers.com](https://docs.weirdfingers.com)
665
+ - **Discord**: [discord.gg/rvVuHyuPEx](https://discord.gg/rvVuHyuPEx)
666
+
667
+ ## Community & Social
668
+
669
+ Join the Weirdfingers community:
670
+
671
+ - **TikTok**: [https://www.tiktok.com/@weirdfingers](https://www.tiktok.com/@weirdfingers)
672
+ - **X (Twitter)**: [https://x.com/_Weirdfingers_](https://x.com/_Weirdfingers_)
673
+ - **YouTube**: [https://www.youtube.com/@Weirdfingers](https://www.youtube.com/@Weirdfingers)
674
+ - **Instagram**: [https://www.instagram.com/_weirdfingers_/](https://www.instagram.com/_weirdfingers_/)
675
+
676
+ ---
677
+
678
+ Built with ❤️ by the Weirdfingers team