@goscribe/server 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ANALYSIS_PROGRESS_SPEC.md +463 -0
- package/PROGRESS_QUICK_REFERENCE.md +239 -0
- package/dist/lib/ai-session.d.ts +20 -9
- package/dist/lib/ai-session.js +316 -80
- package/dist/lib/auth.d.ts +35 -2
- package/dist/lib/auth.js +88 -15
- package/dist/lib/env.d.ts +32 -0
- package/dist/lib/env.js +46 -0
- package/dist/lib/errors.d.ts +33 -0
- package/dist/lib/errors.js +78 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +9 -11
- package/dist/lib/logger.d.ts +62 -0
- package/dist/lib/logger.js +342 -0
- package/dist/lib/podcast-prompts.d.ts +43 -0
- package/dist/lib/podcast-prompts.js +135 -0
- package/dist/lib/pusher.d.ts +1 -0
- package/dist/lib/pusher.js +14 -2
- package/dist/lib/storage.d.ts +3 -3
- package/dist/lib/storage.js +51 -47
- package/dist/lib/validation.d.ts +51 -0
- package/dist/lib/validation.js +64 -0
- package/dist/routers/_app.d.ts +697 -111
- package/dist/routers/_app.js +5 -0
- package/dist/routers/auth.d.ts +11 -1
- package/dist/routers/chat.d.ts +11 -1
- package/dist/routers/flashcards.d.ts +205 -6
- package/dist/routers/flashcards.js +144 -66
- package/dist/routers/members.d.ts +165 -0
- package/dist/routers/members.js +531 -0
- package/dist/routers/podcast.d.ts +78 -63
- package/dist/routers/podcast.js +330 -393
- package/dist/routers/studyguide.d.ts +11 -1
- package/dist/routers/worksheets.d.ts +124 -13
- package/dist/routers/worksheets.js +123 -50
- package/dist/routers/workspace.d.ts +213 -26
- package/dist/routers/workspace.js +303 -181
- package/dist/server.js +12 -4
- package/dist/services/flashcard-progress.service.d.ts +183 -0
- package/dist/services/flashcard-progress.service.js +383 -0
- package/dist/services/flashcard.service.d.ts +183 -0
- package/dist/services/flashcard.service.js +224 -0
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +107 -0
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +326 -0
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +295 -0
- package/dist/trpc.d.ts +13 -2
- package/dist/trpc.js +55 -6
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.js +1 -0
- package/package.json +3 -2
- package/prisma/schema.prisma +142 -4
- package/src/lib/ai-session.ts +356 -85
- package/src/lib/auth.ts +113 -19
- package/src/lib/env.ts +59 -0
- package/src/lib/errors.ts +92 -0
- package/src/lib/inference.ts +11 -11
- package/src/lib/logger.ts +405 -0
- package/src/lib/pusher.ts +15 -3
- package/src/lib/storage.ts +56 -51
- package/src/lib/validation.ts +75 -0
- package/src/routers/_app.ts +5 -0
- package/src/routers/chat.ts +2 -23
- package/src/routers/flashcards.ts +108 -24
- package/src/routers/members.ts +586 -0
- package/src/routers/podcast.ts +385 -420
- package/src/routers/worksheets.ts +117 -35
- package/src/routers/workspace.ts +328 -195
- package/src/server.ts +13 -4
- package/src/services/flashcard-progress.service.ts +541 -0
- package/src/trpc.ts +59 -6
- package/src/types/index.ts +165 -0
- package/AUTH_FRONTEND_SPEC.md +0 -21
- package/CHAT_FRONTEND_SPEC.md +0 -474
- package/DATABASE_SETUP.md +0 -165
- package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
- package/PODCAST_FRONTEND_SPEC.md +0 -595
- package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
- package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
- package/WORKSPACE_FRONTEND_SPEC.md +0 -47
- package/test-ai-integration.js +0 -134
package/PODCAST_FRONTEND_SPEC.md
DELETED
|
@@ -1,595 +0,0 @@
|
|
|
1
|
-
# Podcast Frontend Integration Specification
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
This document outlines the frontend requirements for integrating with the podcast functionality, including real-time updates via Pusher, UI components, and user interactions.
|
|
5
|
-
|
|
6
|
-
## User Prompt Field
|
|
7
|
-
The `userPrompt` field is the primary input for podcast generation. Users provide a prompt describing:
|
|
8
|
-
- The topic they want the podcast to cover
|
|
9
|
-
- Specific questions they want answered
|
|
10
|
-
- The type of content they're looking for
|
|
11
|
-
- Any particular angle or perspective they want
|
|
12
|
-
|
|
13
|
-
Examples of user prompts:
|
|
14
|
-
- "Create a podcast about the history of artificial intelligence"
|
|
15
|
-
- "Explain quantum computing in simple terms for beginners"
|
|
16
|
-
- "Discuss the impact of social media on mental health"
|
|
17
|
-
- "Create a podcast episode about sustainable living practices"
|
|
18
|
-
|
|
19
|
-
The AI will use this prompt to generate complete podcast content, including all segments, scripts, and structure.
|
|
20
|
-
|
|
21
|
-
## Audio Joining Functionality
|
|
22
|
-
The podcast system now generates both individual segment audio files and a complete joined episode audio file:
|
|
23
|
-
|
|
24
|
-
### Individual Segments
|
|
25
|
-
- Each segment is generated as a separate audio file
|
|
26
|
-
- Useful for segment-by-segment playback and editing
|
|
27
|
-
- Allows users to jump to specific parts of the episode
|
|
28
|
-
|
|
29
|
-
### Full Episode Audio
|
|
30
|
-
- All segments are joined into a single continuous audio file
|
|
31
|
-
- Provides a seamless listening experience
|
|
32
|
-
- Useful for traditional podcast playback
|
|
33
|
-
- Stored as a separate file with its own signed URL
|
|
34
|
-
|
|
35
|
-
### Audio Joining Process
|
|
36
|
-
1. After all individual segments are generated
|
|
37
|
-
2. Audio buffers are concatenated in the correct order
|
|
38
|
-
3. The joined audio is uploaded to cloud storage
|
|
39
|
-
4. A signed URL is generated for the full episode
|
|
40
|
-
5. Both individual segments and full episode remain available
|
|
41
|
-
|
|
42
|
-
## Real-Time Events (Pusher)
|
|
43
|
-
|
|
44
|
-
### Event Channels
|
|
45
|
-
All podcast events are broadcast on the channel: `workspace_{workspaceId}`
|
|
46
|
-
|
|
47
|
-
### Event Types
|
|
48
|
-
|
|
49
|
-
#### 1. Podcast Generation Events
|
|
50
|
-
```typescript
|
|
51
|
-
// Generation Start
|
|
52
|
-
{
|
|
53
|
-
event: 'workspace_{workspaceId}_podcast_generation_start',
|
|
54
|
-
data: {
|
|
55
|
-
title: string
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Structure Complete
|
|
60
|
-
{
|
|
61
|
-
event: 'workspace_{workspaceId}_podcast_structure_complete',
|
|
62
|
-
data: {
|
|
63
|
-
segmentsCount: number
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Audio Generation Start
|
|
68
|
-
{
|
|
69
|
-
event: 'workspace_{workspaceId}_podcast_audio_generation_start',
|
|
70
|
-
data: {
|
|
71
|
-
totalSegments: number
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Segment Progress
|
|
76
|
-
{
|
|
77
|
-
event: 'workspace_{workspaceId}_podcast_segment_progress',
|
|
78
|
-
data: {
|
|
79
|
-
currentSegment: number,
|
|
80
|
-
totalSegments: number,
|
|
81
|
-
segmentTitle: string
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Audio Generation Complete
|
|
86
|
-
{
|
|
87
|
-
event: 'workspace_{workspaceId}_podcast_audio_generation_complete',
|
|
88
|
-
data: {
|
|
89
|
-
totalSegments: number,
|
|
90
|
-
totalDuration: number
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Audio Joining Start
|
|
95
|
-
{
|
|
96
|
-
event: 'workspace_{workspaceId}_podcast_audio_joining_start',
|
|
97
|
-
data: {
|
|
98
|
-
totalSegments: number
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Audio Joining Complete
|
|
103
|
-
{
|
|
104
|
-
event: 'workspace_{workspaceId}_podcast_audio_joining_complete',
|
|
105
|
-
data: {
|
|
106
|
-
fullEpisodeObjectKey: string
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Audio Joining Error
|
|
111
|
-
{
|
|
112
|
-
event: 'workspace_{workspaceId}_podcast_audio_joining_error',
|
|
113
|
-
data: {
|
|
114
|
-
error: string
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Summary Complete
|
|
119
|
-
{
|
|
120
|
-
event: 'workspace_{workspaceId}_podcast_summary_complete',
|
|
121
|
-
data: {
|
|
122
|
-
summaryGenerated: boolean
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Generation Complete
|
|
127
|
-
{
|
|
128
|
-
event: 'workspace_{workspaceId}_podcast_ended',
|
|
129
|
-
data: {
|
|
130
|
-
artifactId: string,
|
|
131
|
-
title: string,
|
|
132
|
-
status: 'completed'
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
#### 2. Segment Regeneration Events
|
|
138
|
-
```typescript
|
|
139
|
-
// Regeneration Start
|
|
140
|
-
{
|
|
141
|
-
event: 'workspace_{workspaceId}_podcast_segment_regeneration_start',
|
|
142
|
-
data: {
|
|
143
|
-
segmentId: string,
|
|
144
|
-
segmentTitle: string
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Regeneration Complete
|
|
149
|
-
{
|
|
150
|
-
event: 'workspace_{workspaceId}_podcast_segment_regeneration_complete',
|
|
151
|
-
data: {
|
|
152
|
-
segmentId: string,
|
|
153
|
-
segmentTitle: string,
|
|
154
|
-
duration: number
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Full Episode Regeneration Start (after segment update)
|
|
159
|
-
{
|
|
160
|
-
event: 'workspace_{workspaceId}_podcast_full_episode_regeneration_start',
|
|
161
|
-
data: {
|
|
162
|
-
reason: 'segment_updated'
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Full Episode Regeneration Complete
|
|
167
|
-
{
|
|
168
|
-
event: 'workspace_{workspaceId}_podcast_full_episode_regeneration_complete',
|
|
169
|
-
data: {
|
|
170
|
-
fullEpisodeObjectKey: string
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Full Episode Regeneration Error
|
|
175
|
-
{
|
|
176
|
-
event: 'workspace_{workspaceId}_podcast_full_episode_regeneration_error',
|
|
177
|
-
data: {
|
|
178
|
-
error: string
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
#### 3. Episode Deletion Events
|
|
184
|
-
```typescript
|
|
185
|
-
// Deletion Start
|
|
186
|
-
{
|
|
187
|
-
event: 'workspace_{workspaceId}_podcast_deletion_start',
|
|
188
|
-
data: {
|
|
189
|
-
episodeId: string,
|
|
190
|
-
episodeTitle: string
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Deletion Complete
|
|
195
|
-
{
|
|
196
|
-
event: 'workspace_{workspaceId}_podcast_deletion_complete',
|
|
197
|
-
data: {
|
|
198
|
-
episodeId: string,
|
|
199
|
-
episodeTitle: string
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
#### 4. Error Events
|
|
205
|
-
```typescript
|
|
206
|
-
// General Error
|
|
207
|
-
{
|
|
208
|
-
event: 'workspace_{workspaceId}_podcast_error',
|
|
209
|
-
data: {
|
|
210
|
-
error: string,
|
|
211
|
-
analysisType: 'podcast',
|
|
212
|
-
timestamp: string
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Segment Error
|
|
217
|
-
{
|
|
218
|
-
event: 'workspace_{workspaceId}_podcast_segment_error',
|
|
219
|
-
data: {
|
|
220
|
-
segmentIndex: number,
|
|
221
|
-
error: string
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Summary Error
|
|
226
|
-
{
|
|
227
|
-
event: 'workspace_{workspaceId}_podcast_summary_error',
|
|
228
|
-
data: {
|
|
229
|
-
error: string
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
## UI Components
|
|
235
|
-
|
|
236
|
-
### 1. Podcast Generation Form
|
|
237
|
-
```typescript
|
|
238
|
-
interface PodcastGenerationForm {
|
|
239
|
-
title: string;
|
|
240
|
-
description?: string;
|
|
241
|
-
userPrompt: string;
|
|
242
|
-
voice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
|
|
243
|
-
speed: number; // 0.25 - 4.0
|
|
244
|
-
generateIntro: boolean;
|
|
245
|
-
generateOutro: boolean;
|
|
246
|
-
segmentByTopics: boolean;
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**Required Fields:**
|
|
251
|
-
- Title (required)
|
|
252
|
-
- User Prompt (required) - The topic, question, or request for the podcast
|
|
253
|
-
- Voice (default: 'nova')
|
|
254
|
-
- Speed (default: 1.0)
|
|
255
|
-
|
|
256
|
-
**Optional Fields:**
|
|
257
|
-
- Description
|
|
258
|
-
- Generate Intro (default: true)
|
|
259
|
-
- Generate Outro (default: true)
|
|
260
|
-
- Segment by Topics (default: true)
|
|
261
|
-
|
|
262
|
-
### 2. Podcast List View
|
|
263
|
-
```typescript
|
|
264
|
-
interface PodcastEpisode {
|
|
265
|
-
id: string;
|
|
266
|
-
title: string;
|
|
267
|
-
description?: string;
|
|
268
|
-
metadata: {
|
|
269
|
-
totalDuration: number;
|
|
270
|
-
voice: string;
|
|
271
|
-
speed: number;
|
|
272
|
-
segments: PodcastSegment[];
|
|
273
|
-
fullEpisodeObjectKey?: string; // Reference to the full joined episode audio
|
|
274
|
-
summary: {
|
|
275
|
-
executiveSummary: string;
|
|
276
|
-
learningObjectives: string[];
|
|
277
|
-
keyConcepts: string[];
|
|
278
|
-
followUpActions: string[];
|
|
279
|
-
targetAudience: string;
|
|
280
|
-
prerequisites: string[];
|
|
281
|
-
tags: string[];
|
|
282
|
-
};
|
|
283
|
-
generatedAt: string;
|
|
284
|
-
};
|
|
285
|
-
segments: {
|
|
286
|
-
id: string;
|
|
287
|
-
title: string;
|
|
288
|
-
audioUrl?: string;
|
|
289
|
-
objectKey?: string;
|
|
290
|
-
startTime: number;
|
|
291
|
-
duration: number;
|
|
292
|
-
order: number;
|
|
293
|
-
}[];
|
|
294
|
-
fullEpisodeUrl?: string; // Signed URL for the full episode
|
|
295
|
-
createdAt: string;
|
|
296
|
-
updatedAt: string;
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### 3. Podcast Player Component
|
|
301
|
-
```typescript
|
|
302
|
-
interface PodcastPlayer {
|
|
303
|
-
currentSegment: number;
|
|
304
|
-
isPlaying: boolean;
|
|
305
|
-
currentTime: number;
|
|
306
|
-
duration: number;
|
|
307
|
-
segments: PodcastSegment[];
|
|
308
|
-
onSegmentChange: (segmentId: string) => void;
|
|
309
|
-
onPlayPause: () => void;
|
|
310
|
-
onSeek: (time: number) => void;
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### 4. Segment Editor
|
|
315
|
-
```typescript
|
|
316
|
-
interface SegmentEditor {
|
|
317
|
-
segmentId: string;
|
|
318
|
-
title: string;
|
|
319
|
-
content: string;
|
|
320
|
-
keyPoints: string[];
|
|
321
|
-
order: number;
|
|
322
|
-
onSave: (segment: Partial<PodcastSegment>) => void;
|
|
323
|
-
onRegenerate: (newContent?: string) => void;
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
## API Endpoints
|
|
328
|
-
|
|
329
|
-
### 1. List Episodes
|
|
330
|
-
```typescript
|
|
331
|
-
// GET /trpc/podcast.listEpisodes
|
|
332
|
-
{
|
|
333
|
-
input: {
|
|
334
|
-
workspaceId: string;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### 2. Get Episode
|
|
340
|
-
```typescript
|
|
341
|
-
// GET /trpc/podcast.getEpisode
|
|
342
|
-
{
|
|
343
|
-
input: {
|
|
344
|
-
episodeId: string;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### 3. Generate Episode
|
|
350
|
-
```typescript
|
|
351
|
-
// POST /trpc/podcast.generateEpisode
|
|
352
|
-
{
|
|
353
|
-
input: {
|
|
354
|
-
workspaceId: string;
|
|
355
|
-
podcastData: PodcastGenerationForm;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### 4. Get Episode Schema
|
|
361
|
-
```typescript
|
|
362
|
-
// GET /trpc/podcast.getEpisodeSchema
|
|
363
|
-
{
|
|
364
|
-
input: {
|
|
365
|
-
episodeId: string;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### 5. Update Episode
|
|
371
|
-
```typescript
|
|
372
|
-
// POST /trpc/podcast.updateEpisode
|
|
373
|
-
{
|
|
374
|
-
input: {
|
|
375
|
-
episodeId: string;
|
|
376
|
-
title?: string;
|
|
377
|
-
description?: string;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### 6. Delete Episode
|
|
383
|
-
```typescript
|
|
384
|
-
// POST /trpc/podcast.deleteEpisode
|
|
385
|
-
{
|
|
386
|
-
input: {
|
|
387
|
-
episodeId: string;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### 7. Get Available Voices
|
|
393
|
-
```typescript
|
|
394
|
-
// GET /trpc/podcast.getAvailableVoices
|
|
395
|
-
// No input required
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### 8. Get Signed URLs
|
|
399
|
-
```typescript
|
|
400
|
-
// GET /trpc/podcast.getSignedUrls
|
|
401
|
-
{
|
|
402
|
-
input: {
|
|
403
|
-
episodeId: string;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
### 9. Get Full Episode URL
|
|
409
|
-
```typescript
|
|
410
|
-
// GET /trpc/podcast.getFullEpisodeUrl
|
|
411
|
-
{
|
|
412
|
-
input: {
|
|
413
|
-
episodeId: string;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### 10. Regenerate Segment
|
|
419
|
-
```typescript
|
|
420
|
-
// POST /trpc/podcast.regenerateSegment
|
|
421
|
-
{
|
|
422
|
-
input: {
|
|
423
|
-
episodeId: string;
|
|
424
|
-
segmentId: string;
|
|
425
|
-
newContent?: string; // Optional: new content for the segment
|
|
426
|
-
voice?: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
|
|
427
|
-
speed?: number; // 0.25 - 4.0
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
## User Experience Flow
|
|
433
|
-
|
|
434
|
-
### 1. Podcast Generation
|
|
435
|
-
1. User fills out podcast generation form with:
|
|
436
|
-
- Title for the podcast
|
|
437
|
-
- User prompt describing the topic, question, or request
|
|
438
|
-
- Optional description
|
|
439
|
-
- Voice and speed preferences
|
|
440
|
-
- Generation options (intro, outro, segmentation)
|
|
441
|
-
2. Submit form → show loading state
|
|
442
|
-
3. Listen for real-time progress updates:
|
|
443
|
-
- Generation start
|
|
444
|
-
- Structure complete (AI creates episode content from prompt)
|
|
445
|
-
- Audio generation start
|
|
446
|
-
- Segment progress (update progress bar)
|
|
447
|
-
- Audio generation complete
|
|
448
|
-
- Audio joining start (combining segments into full episode)
|
|
449
|
-
- Audio joining complete
|
|
450
|
-
- Summary complete
|
|
451
|
-
- Generation complete
|
|
452
|
-
4. Redirect to podcast player or show success message
|
|
453
|
-
|
|
454
|
-
### 2. Podcast Playback
|
|
455
|
-
1. Load episode data
|
|
456
|
-
2. Get fresh signed URLs for audio segments
|
|
457
|
-
3. Initialize audio player with segments
|
|
458
|
-
4. Handle segment navigation
|
|
459
|
-
5. Update progress indicators
|
|
460
|
-
|
|
461
|
-
### 3. Segment Management
|
|
462
|
-
1. Display segments in list/grid view
|
|
463
|
-
2. Allow editing segment content
|
|
464
|
-
3. Provide regenerate option for individual segments
|
|
465
|
-
4. Show regeneration progress:
|
|
466
|
-
- Segment regeneration start
|
|
467
|
-
- Segment regeneration complete
|
|
468
|
-
- Full episode regeneration start (to sync all segments)
|
|
469
|
-
- Full episode regeneration complete
|
|
470
|
-
5. Update audio URLs after regeneration (both individual segments and full episode)
|
|
471
|
-
|
|
472
|
-
### 4. Episode Management
|
|
473
|
-
1. List all episodes with metadata
|
|
474
|
-
2. Allow editing episode title/description
|
|
475
|
-
3. Provide delete option with confirmation
|
|
476
|
-
4. Show deletion progress
|
|
477
|
-
|
|
478
|
-
## Error Handling
|
|
479
|
-
|
|
480
|
-
### 1. Generation Errors
|
|
481
|
-
- Display error message with retry option
|
|
482
|
-
- Show which step failed
|
|
483
|
-
- Provide fallback options
|
|
484
|
-
|
|
485
|
-
### 2. Audio Loading Errors
|
|
486
|
-
- Retry loading audio files
|
|
487
|
-
- Show placeholder for failed segments
|
|
488
|
-
- Provide manual refresh option
|
|
489
|
-
|
|
490
|
-
### 3. Network Errors
|
|
491
|
-
- Implement retry logic
|
|
492
|
-
- Show offline indicators
|
|
493
|
-
- Cache episode data when possible
|
|
494
|
-
|
|
495
|
-
## Loading States
|
|
496
|
-
|
|
497
|
-
### 1. Generation Progress
|
|
498
|
-
```typescript
|
|
499
|
-
interface GenerationProgress {
|
|
500
|
-
stage: 'structuring' | 'generating_audio' | 'creating_summary' | 'complete';
|
|
501
|
-
currentSegment?: number;
|
|
502
|
-
totalSegments?: number;
|
|
503
|
-
progress: number; // 0-100
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### 2. Segment Regeneration
|
|
508
|
-
```typescript
|
|
509
|
-
interface SegmentRegeneration {
|
|
510
|
-
segmentId: string;
|
|
511
|
-
status: 'pending' | 'generating' | 'complete' | 'error';
|
|
512
|
-
progress?: number;
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
## Accessibility Requirements
|
|
517
|
-
|
|
518
|
-
### 1. Audio Controls
|
|
519
|
-
- Keyboard navigation for play/pause
|
|
520
|
-
- Screen reader support for progress
|
|
521
|
-
- Volume controls with labels
|
|
522
|
-
|
|
523
|
-
### 2. Form Accessibility
|
|
524
|
-
- Proper form labels
|
|
525
|
-
- Error message associations
|
|
526
|
-
- Required field indicators
|
|
527
|
-
|
|
528
|
-
### 3. Navigation
|
|
529
|
-
- Skip links for main content
|
|
530
|
-
- Focus management
|
|
531
|
-
- ARIA labels for interactive elements
|
|
532
|
-
|
|
533
|
-
## Performance Considerations
|
|
534
|
-
|
|
535
|
-
### 1. Audio Loading
|
|
536
|
-
- Lazy load audio segments
|
|
537
|
-
- Preload next segment
|
|
538
|
-
- Implement audio caching
|
|
539
|
-
|
|
540
|
-
### 2. Real-time Updates
|
|
541
|
-
- Debounce frequent updates
|
|
542
|
-
- Batch UI updates
|
|
543
|
-
- Optimize re-renders
|
|
544
|
-
|
|
545
|
-
### 3. Data Management
|
|
546
|
-
- Implement pagination for episode lists
|
|
547
|
-
- Cache episode metadata
|
|
548
|
-
- Optimize image/audio loading
|
|
549
|
-
|
|
550
|
-
## Testing Requirements
|
|
551
|
-
|
|
552
|
-
### 1. Unit Tests
|
|
553
|
-
- Component rendering
|
|
554
|
-
- Event handling
|
|
555
|
-
- Form validation
|
|
556
|
-
|
|
557
|
-
### 2. Integration Tests
|
|
558
|
-
- API interactions
|
|
559
|
-
- Real-time event handling
|
|
560
|
-
- Audio playback
|
|
561
|
-
|
|
562
|
-
### 3. E2E Tests
|
|
563
|
-
- Complete podcast generation flow
|
|
564
|
-
- Playback functionality
|
|
565
|
-
- Error scenarios
|
|
566
|
-
|
|
567
|
-
## Browser Support
|
|
568
|
-
|
|
569
|
-
### Required
|
|
570
|
-
- Chrome 90+
|
|
571
|
-
- Firefox 88+
|
|
572
|
-
- Safari 14+
|
|
573
|
-
- Edge 90+
|
|
574
|
-
|
|
575
|
-
### Audio Support
|
|
576
|
-
- Web Audio API
|
|
577
|
-
- Media Source Extensions (for streaming)
|
|
578
|
-
- AudioContext support
|
|
579
|
-
|
|
580
|
-
## Security Considerations
|
|
581
|
-
|
|
582
|
-
### 1. Audio URLs
|
|
583
|
-
- Signed URLs expire after 24 hours
|
|
584
|
-
- Implement URL refresh logic
|
|
585
|
-
- Handle expired URL errors
|
|
586
|
-
|
|
587
|
-
### 2. User Permissions
|
|
588
|
-
- Verify workspace ownership
|
|
589
|
-
- Validate episode access
|
|
590
|
-
- Sanitize user inputs
|
|
591
|
-
|
|
592
|
-
### 3. Content Security
|
|
593
|
-
- Validate audio file types
|
|
594
|
-
- Implement file size limits
|
|
595
|
-
- Sanitize episode metadata
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
## Study Guide Frontend Spec
|
|
2
|
-
|
|
3
|
-
### Endpoints (tRPC)
|
|
4
|
-
- **studyguide.get**: `{ workspaceId: string }` → `{ artifactId: string; title: string; latestVersion: { id: string; content: string; data?: Record<string, unknown> | null; version: number; createdById?: string | null; createdAt: Date } | null }`
|
|
5
|
-
- **studyguide.edit**: `{ workspaceId: string; studyGuideId?: string; content: string; data?: Record<string, unknown>; title?: string }` → `{ artifactId: string; version: { id: string; version: number } }`
|
|
6
|
-
|
|
7
|
-
### Behavior
|
|
8
|
-
- `get` will create a default study guide (Editor.js starter block) if none exists.
|
|
9
|
-
- `edit` creates a new artifact version and optionally renames the artifact.
|
|
10
|
-
|
|
11
|
-
### Types (simplified)
|
|
12
|
-
- **StudyGuideArtifact**: `{ id: string; workspaceId: string; type: 'STUDY_GUIDE'; title: string }`
|
|
13
|
-
- **StudyGuideVersion**: `{ id: string; artifactId: string; content: string; data?: Record<string, unknown> | null; version: number; createdById?: string | null; createdAt: Date }`
|
|
14
|
-
|
|
15
|
-
### UX Notes
|
|
16
|
-
- Store Editor.js document JSON in `content`.
|
|
17
|
-
- On save, call `studyguide.edit` (debounce to avoid excessive versions).
|
|
18
|
-
- Handle null `latestVersion` gracefully (should be rare due to auto-create).
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
## Worksheets Frontend Spec
|
|
2
|
-
|
|
3
|
-
### Endpoints (tRPC)
|
|
4
|
-
- **worksheets.list**: `{ workspaceId: string }` → `Worksheet[]` (latest version included, questions merged with user progress)
|
|
5
|
-
- **worksheets.create**: `{ workspaceId: string; title: string; description?: string; difficulty?: 'EASY'|'MEDIUM'|'HARD'; estimatedTime?: string; problems?: { question: string; answer: string; type?: QuestionType; options?: string[] }[] }` → `Worksheet & { questions: Question[] }`
|
|
6
|
-
- **worksheets.get**: `{ worksheetId: string }` → `Worksheet & { questions: Question[] }` (questions merged with progress)
|
|
7
|
-
- **worksheets.createWorksheetQuestion**: `{ worksheetId: string; prompt: string; answer?: string; type?: QuestionType; difficulty?: Difficulty; order?: number; meta?: Record<string, unknown> }` → `Question`
|
|
8
|
-
- **worksheets.updateWorksheetQuestion**: `{ worksheetQuestionId: string; prompt?: string; answer?: string; type?: QuestionType; difficulty?: Difficulty; order?: number; meta?: Record<string, unknown> }` → `Question`
|
|
9
|
-
- **worksheets.deleteWorksheetQuestion**: `{ worksheetQuestionId: string }` → `true`
|
|
10
|
-
- **worksheets.updateProblemStatus**: `{ problemId: string; completed: boolean; answer?: string }` → `WorksheetQuestionProgress`
|
|
11
|
-
- **worksheets.getProgress**: `{ worksheetId: string }` → `WorksheetQuestionProgress[]`
|
|
12
|
-
- **worksheets.update**: `{ id: string; title?: string; description?: string; difficulty?: Difficulty; estimatedTime?: string; problems?: { id?: string; question: string; answer: string; type?: QuestionType; options?: string[] }[] }` → `Worksheet & { questions: Question[] }`
|
|
13
|
-
- **worksheets.delete**: `{ id: string }` → `true`
|
|
14
|
-
|
|
15
|
-
### Types (simplified)
|
|
16
|
-
- **Worksheet**: `{ id: string; workspaceId: string; type: 'WORKSHEET'; title: string; description?: string | null; difficulty?: 'EASY'|'MEDIUM'|'HARD' | null; estimatedTime?: string | null; createdAt: Date; updatedAt: Date }`
|
|
17
|
-
- **Question**: `{ id: string; artifactId: string; prompt: string; answer?: string | null; type: QuestionType; difficulty: Difficulty; order: number; meta?: { options?: string[]; completed?: boolean; userAnswer?: string | null; completedAt?: string | Date | null } }`
|
|
18
|
-
- **QuestionType**: `'MULTIPLE_CHOICE'|'TEXT'|'NUMERIC'|'TRUE_FALSE'|'MATCHING'|'FILL_IN_THE_BLANK'`
|
|
19
|
-
- **Difficulty**: `'EASY'|'MEDIUM'|'HARD'`
|
|
20
|
-
- **WorksheetQuestionProgress**: `{ id: string; worksheetQuestionId: string; userId: string; completed: boolean; userAnswer?: string | null; completedAt?: Date | null; attempts: number }`
|
|
21
|
-
|
|
22
|
-
### UX Notes
|
|
23
|
-
- When listing or fetching a worksheet, question `meta` already contains per-user progress fields.
|
|
24
|
-
- For multiple-choice, render `meta.options` if present.
|
|
25
|
-
- Use optimistic UI for creating/updating/deleting questions.
|
|
26
|
-
- Respect ordering via `order`.
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
## Workspace Frontend Spec
|
|
2
|
-
|
|
3
|
-
### Endpoints (tRPC)
|
|
4
|
-
- **workspace.list**: `{ parentId?: string }` → `{ workspaces: Workspace[], folders: Folder[] }`
|
|
5
|
-
- **workspace.create**: `{ name: string; description?: string; parentId?: string }` → `Workspace`
|
|
6
|
-
- **workspace.createFolder**: `{ name: string; parentId?: string }` → `Folder`
|
|
7
|
-
- **workspace.get**: `{ id: string }` → `Workspace & { artifacts: Artifact[]; folder?: Folder; uploads: FileAsset[] }`
|
|
8
|
-
- **workspace.share**: `{ id: string }` → `{ shareLink: string } | void`
|
|
9
|
-
- **workspace.join**: `{ shareLink: string }` → `{ id; title; description; ownerId; createdAt; updatedAt }`
|
|
10
|
-
- **workspace.update**: `{ id: string; name?: string; description?: string }` → `Workspace`
|
|
11
|
-
- **workspace.delete**: `{ id: string }` → `true`
|
|
12
|
-
- **workspace.getFolderInformation**: `{ id: string }` → `{ folder: Folder; parents: Folder[] }`
|
|
13
|
-
- **workspace.uploadFiles**: `{ id: string; files: { filename: string; contentType: string; size: number }[] }` → `{ fileId: string; uploadUrl: string }[]`
|
|
14
|
-
- **workspace.deleteFiles**: `{ id: string; fileId: string[] }` → `true`
|
|
15
|
-
- **workspace.uploadAndAnalyzeMedia**: `{ workspaceId: string; file: { filename: string; contentType: string; size: number; content: string /* base64 */ }; generateStudyGuide?: boolean; generateFlashcards?: boolean; generateWorksheet?: boolean }` → `{ filename: string; artifacts: { studyGuide: Artifact | null; flashcards: Artifact | null; worksheet: Artifact | null } }`
|
|
16
|
-
- **workspace.search**: `{ query: string; limit?: number }` → `Workspace[]`
|
|
17
|
-
|
|
18
|
-
### Types (simplified)
|
|
19
|
-
- **Workspace**: `{ id: string; title: string; description?: string | null; ownerId: string; folderId?: string | null; createdAt: Date; updatedAt: Date; shareLink?: string | null }`
|
|
20
|
-
- **Folder**: `{ id: string; name: string; ownerId: string; parentId?: string | null; createdAt: Date; updatedAt: Date }`
|
|
21
|
-
- **Artifact**: `{ id: string; workspaceId: string; type: string; title: string; createdById?: string | null; createdAt: Date; updatedAt: Date }`
|
|
22
|
-
- **FileAsset**: `{ id: string; userId: string; workspaceId: string; name: string; mimeType: string; size: number; bucket?: string | null; objectKey?: string | null }`
|
|
23
|
-
|
|
24
|
-
### File Upload Flow
|
|
25
|
-
1. Call `workspace.uploadFiles` to get write-signed URLs.
|
|
26
|
-
2. PUT file bytes to each `uploadUrl` with correct `Content-Type`.
|
|
27
|
-
3. Optionally refresh via `workspace.get` to see uploads.
|
|
28
|
-
|
|
29
|
-
### Media Analysis Flow
|
|
30
|
-
1. Call `workspace.uploadAndAnalyzeMedia` with base64 content and desired generation flags.
|
|
31
|
-
2. Subscribe to Pusher `workspace_{workspaceId}` for progress events.
|
|
32
|
-
3. On completion, fetch artifacts via `studyguide`, `worksheets`, and flashcards routers.
|
|
33
|
-
|
|
34
|
-
### Real-Time Events (Pusher)
|
|
35
|
-
- **Channel**: `workspace_{workspaceId}`
|
|
36
|
-
- **Events** (examples):
|
|
37
|
-
- `{workspaceId}_file_analysis_start`, `{workspaceId}_file_analysis_complete`
|
|
38
|
-
- `{workspaceId}_study_guide_load_start`, `{workspaceId}_study_guide_info`
|
|
39
|
-
- `{workspaceId}_flash_card_load_start`, `{workspaceId}_flash_card_info`
|
|
40
|
-
- `{workspaceId}_worksheet_load_start`, `{workspaceId}_worksheet_info`
|
|
41
|
-
- `{workspaceId}_analysis_cleanup_start`, `{workspaceId}_analysis_cleanup_complete`
|
|
42
|
-
- Overall: `{workspaceId}_analysis_ended`
|
|
43
|
-
|
|
44
|
-
### UX Notes
|
|
45
|
-
- Empty state: show create workspace/folder actions.
|
|
46
|
-
- Uploads: show progress per file; deletions from GCS are best-effort.
|
|
47
|
-
- Search: debounce input; pass `limit` (default 20).
|