@weirdfingers/boards 0.2.1 → 0.3.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 +678 -0
- package/dist/index.d.mts +225 -2
- package/dist/index.d.ts +225 -2
- package/dist/index.js +155 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/hooks/useGenerators.ts +6 -2
- package/src/index.ts +5 -0
- package/src/types/generatorSchema.ts +187 -0
- package/src/utils/__tests__/schemaParser.test.ts +576 -0
- package/src/utils/schemaParser.ts +310 -0
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
|