@goscribe/server 1.1.1 → 1.1.3
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/lib/ai-session.d.ts +13 -3
- package/dist/lib/ai-session.js +66 -146
- package/dist/lib/pusher.js +1 -1
- package/dist/routers/_app.d.ts +114 -7
- package/dist/routers/chat.js +2 -23
- package/dist/routers/flashcards.d.ts +25 -1
- package/dist/routers/flashcards.js +0 -14
- package/dist/routers/members.d.ts +18 -0
- package/dist/routers/members.js +14 -1
- package/dist/routers/worksheets.js +5 -4
- package/dist/routers/workspace.d.ts +89 -6
- package/dist/routers/workspace.js +389 -259
- package/dist/services/flashcard-progress.service.d.ts +25 -1
- package/dist/services/flashcard-progress.service.js +70 -31
- package/package.json +2 -2
- package/prisma/schema.prisma +14 -1
- package/src/lib/ai-session.ts +97 -158
- package/src/routers/flashcards.ts +0 -16
- package/src/routers/members.ts +13 -2
- package/src/routers/podcast.ts +0 -1
- package/src/routers/worksheets.ts +3 -2
- package/src/routers/workspace.ts +516 -399
- package/ANALYSIS_PROGRESS_SPEC.md +0 -463
- package/PROGRESS_QUICK_REFERENCE.md +0 -239
- package/dist/lib/podcast-prompts.d.ts +0 -43
- package/dist/lib/podcast-prompts.js +0 -135
- package/dist/routers/ai-session.d.ts +0 -0
- package/dist/routers/ai-session.js +0 -1
- package/dist/services/flashcard.service.d.ts +0 -183
- package/dist/services/flashcard.service.js +0 -224
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +0 -107
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +0 -326
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +0 -295
|
@@ -1,463 +0,0 @@
|
|
|
1
|
-
# Analysis Progress Integration Spec
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
The analysis progress system tracks file upload and analysis status in real-time using both database storage and Pusher events.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 1. Data Structure
|
|
9
|
-
|
|
10
|
-
### `Workspace.analysisProgress` (JSON field)
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
interface AnalysisProgress {
|
|
14
|
-
status: AnalysisStatus;
|
|
15
|
-
filename: string;
|
|
16
|
-
fileType: 'image' | 'pdf';
|
|
17
|
-
startedAt: string; // ISO 8601 timestamp
|
|
18
|
-
completedAt?: string; // ISO 8601 timestamp (only when completed)
|
|
19
|
-
error?: string; // Error message (only when status is 'error')
|
|
20
|
-
steps: {
|
|
21
|
-
fileUpload: StepStatus;
|
|
22
|
-
fileAnalysis: StepStatus;
|
|
23
|
-
studyGuide: StepStatus;
|
|
24
|
-
flashcards: StepStatus;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type AnalysisStatus =
|
|
29
|
-
| 'starting'
|
|
30
|
-
| 'uploading'
|
|
31
|
-
| 'analyzing'
|
|
32
|
-
| 'generating_artifacts'
|
|
33
|
-
| 'generating_study_guide'
|
|
34
|
-
| 'generating_flashcards'
|
|
35
|
-
| 'completed'
|
|
36
|
-
| 'error';
|
|
37
|
-
|
|
38
|
-
type StepStatus =
|
|
39
|
-
| 'pending'
|
|
40
|
-
| 'in_progress'
|
|
41
|
-
| 'completed'
|
|
42
|
-
| 'skipped'
|
|
43
|
-
| 'error';
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 2. Status Flow
|
|
49
|
-
|
|
50
|
-
### Normal Flow (All artifacts enabled)
|
|
51
|
-
```
|
|
52
|
-
starting
|
|
53
|
-
→ uploading
|
|
54
|
-
→ analyzing
|
|
55
|
-
→ generating_artifacts
|
|
56
|
-
→ generating_study_guide
|
|
57
|
-
→ generating_flashcards
|
|
58
|
-
→ completed
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Error Flow
|
|
62
|
-
```
|
|
63
|
-
Any status → error (with error message)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Skipped Artifacts
|
|
67
|
-
If user doesn't select certain artifacts, their steps will be marked as `'skipped'`:
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
// Example: Only flashcards enabled
|
|
71
|
-
{
|
|
72
|
-
status: 'generating_flashcards',
|
|
73
|
-
steps: {
|
|
74
|
-
fileUpload: 'completed',
|
|
75
|
-
fileAnalysis: 'completed',
|
|
76
|
-
studyGuide: 'skipped', // ← Not requested
|
|
77
|
-
flashcards: 'in_progress'
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 3. Real-time Updates (Pusher)
|
|
85
|
-
|
|
86
|
-
### Subscribe to Progress Events
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
import Pusher from 'pusher-js';
|
|
90
|
-
|
|
91
|
-
const pusher = new Pusher('YOUR_PUSHER_KEY', {
|
|
92
|
-
cluster: 'us2',
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const channel = pusher.subscribe(`workspace_${workspaceId}`);
|
|
96
|
-
|
|
97
|
-
channel.bind('analysis_progress', (progress: AnalysisProgress) => {
|
|
98
|
-
console.log('Progress update:', progress);
|
|
99
|
-
|
|
100
|
-
// Update your UI based on progress
|
|
101
|
-
updateProgressUI(progress);
|
|
102
|
-
});
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Event Timing
|
|
106
|
-
- Event is emitted **every time** the progress changes
|
|
107
|
-
- Happens **before** the database write completes
|
|
108
|
-
- Frontend receives updates in real-time as analysis progresses
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## 4. Database Queries (tRPC)
|
|
113
|
-
|
|
114
|
-
### Get Workspace with Progress
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
// Query the workspace to get current progress
|
|
118
|
-
const workspace = await trpc.workspace.get.useQuery({
|
|
119
|
-
id: workspaceId
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Access progress
|
|
123
|
-
const progress = workspace.analysisProgress;
|
|
124
|
-
|
|
125
|
-
if (progress?.status === 'generating_flashcards') {
|
|
126
|
-
// Show flashcards generation UI
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Polling Alternative (if Pusher disconnects)
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
const { data: workspace, refetch } = trpc.workspace.get.useQuery(
|
|
134
|
-
{ id: workspaceId },
|
|
135
|
-
{
|
|
136
|
-
refetchInterval: (data) => {
|
|
137
|
-
// Poll every 2 seconds while analyzing
|
|
138
|
-
return data?.fileBeingAnalyzed ? 2000 : false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
);
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## 5. UI Implementation Examples
|
|
147
|
-
|
|
148
|
-
### React Component Example
|
|
149
|
-
|
|
150
|
-
```tsx
|
|
151
|
-
import { useState, useEffect } from 'react';
|
|
152
|
-
import Pusher from 'pusher-js';
|
|
153
|
-
import { trpc } from '@/lib/trpc';
|
|
154
|
-
|
|
155
|
-
interface Props {
|
|
156
|
-
workspaceId: string;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function AnalysisProgressIndicator({ workspaceId }: Props) {
|
|
160
|
-
const [progress, setProgress] = useState<AnalysisProgress | null>(null);
|
|
161
|
-
const { data: workspace } = trpc.workspace.get.useQuery({ id: workspaceId });
|
|
162
|
-
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
// Initialize with DB state
|
|
165
|
-
if (workspace?.analysisProgress) {
|
|
166
|
-
setProgress(workspace.analysisProgress);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Subscribe to real-time updates
|
|
170
|
-
const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY!, {
|
|
171
|
-
cluster: 'us2',
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const channel = pusher.subscribe(`workspace_${workspaceId}`);
|
|
175
|
-
|
|
176
|
-
channel.bind('analysis_progress', (newProgress: AnalysisProgress) => {
|
|
177
|
-
setProgress(newProgress);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
return () => {
|
|
181
|
-
channel.unbind_all();
|
|
182
|
-
pusher.unsubscribe(`workspace_${workspaceId}`);
|
|
183
|
-
};
|
|
184
|
-
}, [workspaceId, workspace]);
|
|
185
|
-
|
|
186
|
-
if (!progress) return null;
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<div className="progress-indicator">
|
|
190
|
-
<h3>{getStatusMessage(progress.status)}</h3>
|
|
191
|
-
<p>Analyzing: {progress.filename}</p>
|
|
192
|
-
|
|
193
|
-
<div className="steps">
|
|
194
|
-
<Step name="Upload" status={progress.steps.fileUpload} />
|
|
195
|
-
<Step name="Analysis" status={progress.steps.fileAnalysis} />
|
|
196
|
-
<Step name="Study Guide" status={progress.steps.studyGuide} />
|
|
197
|
-
<Step name="Flashcards" status={progress.steps.flashcards} />
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
{progress.error && (
|
|
201
|
-
<div className="error">{progress.error}</div>
|
|
202
|
-
)}
|
|
203
|
-
|
|
204
|
-
{progress.completedAt && (
|
|
205
|
-
<div className="success">
|
|
206
|
-
Completed in {calculateDuration(progress.startedAt, progress.completedAt)}
|
|
207
|
-
</div>
|
|
208
|
-
)}
|
|
209
|
-
</div>
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function getStatusMessage(status: AnalysisStatus): string {
|
|
214
|
-
const messages: Record<AnalysisStatus, string> = {
|
|
215
|
-
starting: 'Initializing...',
|
|
216
|
-
uploading: 'Uploading file...',
|
|
217
|
-
analyzing: 'Analyzing content...',
|
|
218
|
-
generating_artifacts: 'Preparing artifacts...',
|
|
219
|
-
generating_study_guide: 'Creating study guide...',
|
|
220
|
-
generating_flashcards: 'Generating flashcards...',
|
|
221
|
-
completed: 'Analysis complete!',
|
|
222
|
-
error: 'An error occurred',
|
|
223
|
-
};
|
|
224
|
-
return messages[status];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function Step({ name, status }: { name: string; status: StepStatus }) {
|
|
228
|
-
const icon = {
|
|
229
|
-
pending: '⏳',
|
|
230
|
-
in_progress: '🔄',
|
|
231
|
-
completed: '✅',
|
|
232
|
-
skipped: '⏭️',
|
|
233
|
-
error: '❌',
|
|
234
|
-
}[status];
|
|
235
|
-
|
|
236
|
-
return (
|
|
237
|
-
<div className={`step step-${status}`}>
|
|
238
|
-
<span>{icon}</span>
|
|
239
|
-
<span>{name}</span>
|
|
240
|
-
</div>
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function calculateDuration(start: string, end: string): string {
|
|
245
|
-
const ms = new Date(end).getTime() - new Date(start).getTime();
|
|
246
|
-
const seconds = Math.floor(ms / 1000);
|
|
247
|
-
return `${seconds}s`;
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## 6. Progress Percentage Calculation
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
function calculateProgress(progress: AnalysisProgress): number {
|
|
257
|
-
const steps = Object.values(progress.steps);
|
|
258
|
-
const completed = steps.filter(s => s === 'completed').length;
|
|
259
|
-
const total = steps.filter(s => s !== 'skipped').length;
|
|
260
|
-
|
|
261
|
-
return total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Usage
|
|
265
|
-
const percentage = calculateProgress(progress);
|
|
266
|
-
// Returns: 0-100
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
## 7. Backend Endpoint (Reference)
|
|
272
|
-
|
|
273
|
-
### Trigger Analysis
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
// Call this to start analysis
|
|
277
|
-
const result = await trpc.workspace.uploadAndAnalyzeMedia.mutate({
|
|
278
|
-
workspaceId: 'workspace_123',
|
|
279
|
-
file: {
|
|
280
|
-
filename: 'biology-notes.pdf',
|
|
281
|
-
contentType: 'application/pdf',
|
|
282
|
-
size: 1024000,
|
|
283
|
-
content: base64EncodedContent,
|
|
284
|
-
},
|
|
285
|
-
generateStudyGuide: true,
|
|
286
|
-
generateFlashcards: true,
|
|
287
|
-
generateWorksheet: false, // This won't appear in progress
|
|
288
|
-
});
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
---
|
|
292
|
-
|
|
293
|
-
## 8. Common Patterns
|
|
294
|
-
|
|
295
|
-
### Show Modal During Analysis
|
|
296
|
-
|
|
297
|
-
```tsx
|
|
298
|
-
function AnalysisModal({ workspaceId }: { workspaceId: string }) {
|
|
299
|
-
const { data: workspace } = trpc.workspace.get.useQuery({ id: workspaceId });
|
|
300
|
-
const [progress, setProgress] = useState<AnalysisProgress | null>(null);
|
|
301
|
-
|
|
302
|
-
// Subscribe to progress...
|
|
303
|
-
|
|
304
|
-
const isAnalyzing = workspace?.fileBeingAnalyzed ||
|
|
305
|
-
(progress && progress.status !== 'completed' && progress.status !== 'error');
|
|
306
|
-
|
|
307
|
-
if (!isAnalyzing) return null;
|
|
308
|
-
|
|
309
|
-
return (
|
|
310
|
-
<Modal>
|
|
311
|
-
<AnalysisProgressIndicator workspaceId={workspaceId} />
|
|
312
|
-
</Modal>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Redirect on Completion
|
|
318
|
-
|
|
319
|
-
```tsx
|
|
320
|
-
useEffect(() => {
|
|
321
|
-
if (progress?.status === 'completed') {
|
|
322
|
-
setTimeout(() => {
|
|
323
|
-
router.push(`/workspace/${workspaceId}`);
|
|
324
|
-
}, 2000); // Show success message for 2s
|
|
325
|
-
}
|
|
326
|
-
}, [progress?.status]);
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Handle Errors
|
|
330
|
-
|
|
331
|
-
```tsx
|
|
332
|
-
useEffect(() => {
|
|
333
|
-
if (progress?.status === 'error') {
|
|
334
|
-
toast.error(`Analysis failed: ${progress.error}`);
|
|
335
|
-
|
|
336
|
-
// Allow user to retry
|
|
337
|
-
setShowRetryButton(true);
|
|
338
|
-
}
|
|
339
|
-
}, [progress?.status]);
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
---
|
|
343
|
-
|
|
344
|
-
## 9. Testing
|
|
345
|
-
|
|
346
|
-
### Mock Progress States
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
const mockProgress: AnalysisProgress = {
|
|
350
|
-
status: 'generating_flashcards',
|
|
351
|
-
filename: 'test.pdf',
|
|
352
|
-
fileType: 'pdf',
|
|
353
|
-
startedAt: new Date().toISOString(),
|
|
354
|
-
steps: {
|
|
355
|
-
fileUpload: 'completed',
|
|
356
|
-
fileAnalysis: 'completed',
|
|
357
|
-
studyGuide: 'completed',
|
|
358
|
-
flashcards: 'in_progress',
|
|
359
|
-
},
|
|
360
|
-
};
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Simulate Progress
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
const statuses: AnalysisStatus[] = [
|
|
367
|
-
'starting',
|
|
368
|
-
'uploading',
|
|
369
|
-
'analyzing',
|
|
370
|
-
'generating_artifacts',
|
|
371
|
-
'generating_study_guide',
|
|
372
|
-
'generating_flashcards',
|
|
373
|
-
'completed',
|
|
374
|
-
];
|
|
375
|
-
|
|
376
|
-
let index = 0;
|
|
377
|
-
const interval = setInterval(() => {
|
|
378
|
-
setProgress(prev => ({
|
|
379
|
-
...prev!,
|
|
380
|
-
status: statuses[index],
|
|
381
|
-
}));
|
|
382
|
-
|
|
383
|
-
if (++index >= statuses.length) {
|
|
384
|
-
clearInterval(interval);
|
|
385
|
-
}
|
|
386
|
-
}, 2000);
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## 10. Important Notes
|
|
392
|
-
|
|
393
|
-
### ⚠️ Critical Behaviors
|
|
394
|
-
|
|
395
|
-
1. **Pusher is Primary**: Database is backup. Always prioritize Pusher events.
|
|
396
|
-
2. **No Worksheet Tracking**: Worksheets are generated but NOT tracked in progress.
|
|
397
|
-
3. **Boolean Flag**: `workspace.fileBeingAnalyzed` is simpler check for "is analyzing".
|
|
398
|
-
4. **Progress Persistence**: Progress persists in DB even after completion (for history).
|
|
399
|
-
5. **Timestamps**: All timestamps are ISO 8601 strings (use `new Date(timestamp)`).
|
|
400
|
-
|
|
401
|
-
### 🎯 Best Practices
|
|
402
|
-
|
|
403
|
-
1. Show progress modal immediately on file upload
|
|
404
|
-
2. Use optimistic UI updates with Pusher
|
|
405
|
-
3. Fall back to polling if Pusher connection fails
|
|
406
|
-
4. Clear progress UI gracefully on completion (2-3s delay)
|
|
407
|
-
5. Allow users to navigate away (background processing)
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## 11. Complete Example Flow
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
// 1. User uploads file
|
|
415
|
-
const uploadResult = await trpc.workspace.uploadAndAnalyzeMedia.mutate({
|
|
416
|
-
workspaceId,
|
|
417
|
-
file: uploadedFile,
|
|
418
|
-
generateStudyGuide: true,
|
|
419
|
-
generateFlashcards: true,
|
|
420
|
-
generateWorksheet: false,
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// 2. Frontend receives Pusher events automatically:
|
|
424
|
-
// Event 1: { status: 'starting', steps: { fileUpload: 'pending', ... } }
|
|
425
|
-
// Event 2: { status: 'uploading', steps: { fileUpload: 'in_progress', ... } }
|
|
426
|
-
// Event 3: { status: 'analyzing', steps: { fileUpload: 'completed', fileAnalysis: 'in_progress', ... } }
|
|
427
|
-
// ...
|
|
428
|
-
// Event N: { status: 'completed', steps: { all: 'completed', ... }, completedAt: '...' }
|
|
429
|
-
|
|
430
|
-
// 3. UI updates automatically via state
|
|
431
|
-
// 4. On completion, redirect to workspace
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
---
|
|
435
|
-
|
|
436
|
-
## 12. Troubleshooting
|
|
437
|
-
|
|
438
|
-
### Issue: Progress not updating
|
|
439
|
-
- **Check**: Pusher connection status
|
|
440
|
-
- **Check**: Correct channel name `workspace_${workspaceId}`
|
|
441
|
-
- **Check**: Event name is `'analysis_progress'`
|
|
442
|
-
- **Fallback**: Use polling with `refetchInterval`
|
|
443
|
-
|
|
444
|
-
### Issue: Progress shows old data
|
|
445
|
-
- **Solution**: Clear `analysisProgress` on new upload
|
|
446
|
-
- **Solution**: Check `fileBeingAnalyzed` flag first
|
|
447
|
-
|
|
448
|
-
### Issue: Pusher disconnects
|
|
449
|
-
- **Solution**: Implement reconnection logic
|
|
450
|
-
- **Solution**: Fall back to polling
|
|
451
|
-
- **Solution**: Check Pusher key and cluster config
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
## Support
|
|
456
|
-
|
|
457
|
-
For backend changes or questions, check:
|
|
458
|
-
- `/src/routers/workspace.ts` - Main logic
|
|
459
|
-
- `/src/lib/pusher.ts` - Pusher service
|
|
460
|
-
- `/prisma/schema.prisma` - Database schema
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
# Analysis Progress - Quick Reference
|
|
2
|
-
|
|
3
|
-
## 🚀 Quick Start
|
|
4
|
-
|
|
5
|
-
### 1. Subscribe to Progress (Pusher)
|
|
6
|
-
```typescript
|
|
7
|
-
const channel = pusher.subscribe(`workspace_${workspaceId}`);
|
|
8
|
-
channel.bind('analysis_progress', (progress) => {
|
|
9
|
-
console.log(progress.status); // 'uploading', 'analyzing', etc.
|
|
10
|
-
});
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
### 2. Query Current Progress (Database)
|
|
14
|
-
```typescript
|
|
15
|
-
const workspace = await trpc.workspace.get.useQuery({ id: workspaceId });
|
|
16
|
-
const progress = workspace.analysisProgress;
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 📊 Data Structure (TypeScript)
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
interface AnalysisProgress {
|
|
25
|
-
status: 'starting' | 'uploading' | 'analyzing' |
|
|
26
|
-
'generating_artifacts' | 'generating_study_guide' |
|
|
27
|
-
'generating_flashcards' | 'completed' | 'error';
|
|
28
|
-
|
|
29
|
-
filename: string;
|
|
30
|
-
fileType: 'image' | 'pdf';
|
|
31
|
-
startedAt: string;
|
|
32
|
-
completedAt?: string;
|
|
33
|
-
error?: string;
|
|
34
|
-
|
|
35
|
-
steps: {
|
|
36
|
-
fileUpload: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
|
|
37
|
-
fileAnalysis: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
|
|
38
|
-
studyGuide: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
|
|
39
|
-
flashcards: 'pending' | 'in_progress' | 'completed' | 'skipped' | 'error';
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 🔄 Status Flow
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
starting → uploading → analyzing → generating_artifacts
|
|
50
|
-
→ generating_study_guide → generating_flashcards → completed
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Or jump to:** `error` (at any point)
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 📝 Example Progress Objects
|
|
58
|
-
|
|
59
|
-
### Starting
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"status": "starting",
|
|
63
|
-
"filename": "biology.pdf",
|
|
64
|
-
"fileType": "pdf",
|
|
65
|
-
"startedAt": "2025-11-05T10:30:00.000Z",
|
|
66
|
-
"steps": {
|
|
67
|
-
"fileUpload": "pending",
|
|
68
|
-
"fileAnalysis": "pending",
|
|
69
|
-
"studyGuide": "pending",
|
|
70
|
-
"flashcards": "pending"
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### In Progress
|
|
76
|
-
```json
|
|
77
|
-
{
|
|
78
|
-
"status": "generating_flashcards",
|
|
79
|
-
"steps": {
|
|
80
|
-
"fileUpload": "completed",
|
|
81
|
-
"fileAnalysis": "completed",
|
|
82
|
-
"studyGuide": "completed",
|
|
83
|
-
"flashcards": "in_progress"
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Completed
|
|
89
|
-
```json
|
|
90
|
-
{
|
|
91
|
-
"status": "completed",
|
|
92
|
-
"startedAt": "2025-11-05T10:30:00.000Z",
|
|
93
|
-
"completedAt": "2025-11-05T10:35:00.000Z",
|
|
94
|
-
"steps": {
|
|
95
|
-
"fileUpload": "completed",
|
|
96
|
-
"fileAnalysis": "completed",
|
|
97
|
-
"studyGuide": "completed",
|
|
98
|
-
"flashcards": "completed"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Error
|
|
104
|
-
```json
|
|
105
|
-
{
|
|
106
|
-
"status": "error",
|
|
107
|
-
"error": "Failed to analyze pdf: Connection timeout",
|
|
108
|
-
"steps": {
|
|
109
|
-
"fileUpload": "completed",
|
|
110
|
-
"fileAnalysis": "error",
|
|
111
|
-
"studyGuide": "skipped",
|
|
112
|
-
"flashcards": "skipped"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 🎨 UI Helpers
|
|
120
|
-
|
|
121
|
-
### Status Messages
|
|
122
|
-
```typescript
|
|
123
|
-
const messages = {
|
|
124
|
-
starting: 'Initializing...',
|
|
125
|
-
uploading: 'Uploading file...',
|
|
126
|
-
analyzing: 'Analyzing content...',
|
|
127
|
-
generating_artifacts: 'Preparing artifacts...',
|
|
128
|
-
generating_study_guide: 'Creating study guide...',
|
|
129
|
-
generating_flashcards: 'Generating flashcards...',
|
|
130
|
-
completed: 'Analysis complete! ✅',
|
|
131
|
-
error: 'An error occurred ❌',
|
|
132
|
-
};
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Step Icons
|
|
136
|
-
```typescript
|
|
137
|
-
const icons = {
|
|
138
|
-
pending: '⏳',
|
|
139
|
-
in_progress: '🔄',
|
|
140
|
-
completed: '✅',
|
|
141
|
-
skipped: '⏭️',
|
|
142
|
-
error: '❌',
|
|
143
|
-
};
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Progress Percentage
|
|
147
|
-
```typescript
|
|
148
|
-
function getPercentage(progress: AnalysisProgress): number {
|
|
149
|
-
const steps = Object.values(progress.steps);
|
|
150
|
-
const completed = steps.filter(s => s === 'completed').length;
|
|
151
|
-
const total = steps.filter(s => s !== 'skipped').length;
|
|
152
|
-
return Math.round((completed / total) * 100);
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## 🔌 Pusher Setup
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
import Pusher from 'pusher-js';
|
|
162
|
-
|
|
163
|
-
const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY!, {
|
|
164
|
-
cluster: 'us2',
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const channel = pusher.subscribe(`workspace_${workspaceId}`);
|
|
168
|
-
|
|
169
|
-
// Single event type for all progress updates
|
|
170
|
-
channel.bind('analysis_progress', (progress: AnalysisProgress) => {
|
|
171
|
-
updateUI(progress);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Cleanup
|
|
175
|
-
channel.unbind_all();
|
|
176
|
-
pusher.unsubscribe(`workspace_${workspaceId}`);
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## ⚡ Common Checks
|
|
182
|
-
|
|
183
|
-
### Is Analyzing?
|
|
184
|
-
```typescript
|
|
185
|
-
const isAnalyzing = workspace.fileBeingAnalyzed ||
|
|
186
|
-
(progress?.status && !['completed', 'error'].includes(progress.status));
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Is Complete?
|
|
190
|
-
```typescript
|
|
191
|
-
const isComplete = progress?.status === 'completed';
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Has Error?
|
|
195
|
-
```typescript
|
|
196
|
-
const hasError = progress?.status === 'error';
|
|
197
|
-
const errorMessage = progress?.error;
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Duration
|
|
201
|
-
```typescript
|
|
202
|
-
const duration = new Date(progress.completedAt).getTime() -
|
|
203
|
-
new Date(progress.startedAt).getTime();
|
|
204
|
-
const seconds = Math.floor(duration / 1000);
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## 🎯 Best Practices
|
|
210
|
-
|
|
211
|
-
1. ✅ Use Pusher for real-time updates
|
|
212
|
-
2. ✅ Show progress modal immediately
|
|
213
|
-
3. ✅ Use `fileBeingAnalyzed` as simple boolean check
|
|
214
|
-
4. ✅ Handle disconnections with polling fallback
|
|
215
|
-
5. ✅ Display friendly error messages
|
|
216
|
-
6. ❌ Don't rely solely on polling (use Pusher!)
|
|
217
|
-
7. ❌ Don't block UI (allow navigation away)
|
|
218
|
-
8. ❌ Don't track worksheets (they're not in progress)
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## 🐛 Troubleshooting
|
|
223
|
-
|
|
224
|
-
| Issue | Solution |
|
|
225
|
-
|-------|----------|
|
|
226
|
-
| No updates | Check Pusher key, cluster, channel name |
|
|
227
|
-
| Old data | Query workspace again after upload starts |
|
|
228
|
-
| Events missed | Implement polling fallback |
|
|
229
|
-
| Wrong channel | Format: `workspace_${workspaceId}` |
|
|
230
|
-
| Wrong event | Event name: `'analysis_progress'` |
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## 📚 Full Documentation
|
|
235
|
-
|
|
236
|
-
See `ANALYSIS_PROGRESS_SPEC.md` for complete examples and implementation details.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|