@goscribe/server 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +3 -2
- package/prisma/schema.prisma +196 -184
- package/src/lib/ai-session.ts +3 -21
- package/src/routers/auth.ts +50 -2
- package/src/routers/flashcards.ts +0 -16
- package/src/routers/members.ts +27 -6
- package/src/routers/workspace.ts +468 -439
- package/src/server.ts +13 -0
- 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
package/prisma/schema.prisma
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
1
|
generator client {
|
|
3
2
|
provider = "prisma-client-js"
|
|
4
3
|
}
|
|
5
4
|
|
|
6
5
|
datasource db {
|
|
7
|
-
provider
|
|
8
|
-
url
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
9
8
|
directUrl = env("DIRECT_URL") // for shadow db in migrations
|
|
10
9
|
}
|
|
11
10
|
|
|
@@ -39,39 +38,51 @@ enum QuestionType {
|
|
|
39
38
|
// NextAuth-compatible auth models (minimal)
|
|
40
39
|
//
|
|
41
40
|
model User {
|
|
42
|
-
id
|
|
43
|
-
name
|
|
44
|
-
email
|
|
45
|
-
emailVerified
|
|
46
|
-
passwordHash
|
|
47
|
-
|
|
48
|
-
session
|
|
41
|
+
id String @id @default(cuid())
|
|
42
|
+
name String?
|
|
43
|
+
email String? @unique
|
|
44
|
+
emailVerified DateTime?
|
|
45
|
+
passwordHash String? // for credentials login
|
|
46
|
+
profilePicture FileAsset? @relation(fields: [fileAssetId], references: [id])
|
|
47
|
+
session Session[]
|
|
49
48
|
|
|
50
49
|
// Ownership
|
|
51
|
-
folders
|
|
52
|
-
workspaces
|
|
53
|
-
invitedInWorkspaces
|
|
50
|
+
folders Folder[] @relation("UserFolders")
|
|
51
|
+
workspaces Workspace[] @relation("UserWorkspaces")
|
|
52
|
+
invitedInWorkspaces Workspace[] @relation("WorkspaceSharedWith") // many-to-many (deprecated)
|
|
54
53
|
workspaceMemberships WorkspaceMember[] // proper member management
|
|
55
|
-
uploads
|
|
56
|
-
artifacts
|
|
57
|
-
versions
|
|
58
|
-
|
|
54
|
+
uploads FileAsset[] @relation("UserUploads")
|
|
55
|
+
artifacts Artifact[] @relation("UserArtifacts")
|
|
56
|
+
versions ArtifactVersion[] @relation("UserArtifactVersions")
|
|
57
|
+
|
|
59
58
|
// Progress tracking
|
|
60
|
-
flashcardProgress FlashcardProgress[]
|
|
59
|
+
flashcardProgress FlashcardProgress[] @relation("UserFlashcardProgress")
|
|
61
60
|
worksheetQuestionProgress WorksheetQuestionProgress[]
|
|
62
|
-
|
|
61
|
+
|
|
63
62
|
// Invitations
|
|
64
63
|
sentInvitations WorkspaceInvitation[] @relation("UserInvitations")
|
|
65
64
|
|
|
65
|
+
notifications Notification[]
|
|
66
66
|
chats Chat[]
|
|
67
|
-
createdAt DateTime
|
|
68
|
-
updatedAt DateTime
|
|
67
|
+
createdAt DateTime @default(now())
|
|
68
|
+
updatedAt DateTime @updatedAt
|
|
69
|
+
fileAssetId String?
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
model Notification {
|
|
73
|
+
id String @id @default(cuid())
|
|
74
|
+
userId String
|
|
75
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
76
|
+
content String
|
|
77
|
+
read Boolean @default(false)
|
|
78
|
+
createdAt DateTime @default(now())
|
|
79
|
+
updatedAt DateTime @updatedAt
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
model Session {
|
|
72
|
-
id
|
|
73
|
-
userId
|
|
74
|
-
user
|
|
83
|
+
id String @id @default(cuid())
|
|
84
|
+
userId String
|
|
85
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
75
86
|
expires DateTime
|
|
76
87
|
}
|
|
77
88
|
|
|
@@ -87,62 +98,62 @@ model VerificationToken {
|
|
|
87
98
|
// Filesystem-like structure
|
|
88
99
|
//
|
|
89
100
|
model Folder {
|
|
90
|
-
id
|
|
91
|
-
name
|
|
92
|
-
ownerId
|
|
93
|
-
owner
|
|
101
|
+
id String @id @default(cuid())
|
|
102
|
+
name String
|
|
103
|
+
ownerId String
|
|
104
|
+
owner User @relation("UserFolders", fields: [ownerId], references: [id], onDelete: Cascade)
|
|
94
105
|
|
|
95
106
|
// Nested folders
|
|
96
|
-
parentId
|
|
97
|
-
parent
|
|
98
|
-
children
|
|
99
|
-
color
|
|
107
|
+
parentId String?
|
|
108
|
+
parent Folder? @relation("FolderChildren", fields: [parentId], references: [id], onDelete: Cascade)
|
|
109
|
+
children Folder[] @relation("FolderChildren")
|
|
110
|
+
color String @default("#9D00FF")
|
|
100
111
|
|
|
101
112
|
// Files (workspaces) inside folders
|
|
102
113
|
workspaces Workspace[]
|
|
103
114
|
|
|
104
115
|
// Metadata
|
|
105
|
-
createdAt DateTime
|
|
106
|
-
updatedAt DateTime
|
|
116
|
+
createdAt DateTime @default(now())
|
|
117
|
+
updatedAt DateTime @updatedAt
|
|
107
118
|
|
|
108
119
|
// Helpful composite index: folders per owner + parent
|
|
109
120
|
@@index([ownerId, parentId])
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
model Workspace {
|
|
113
|
-
id String
|
|
124
|
+
id String @id @default(cuid())
|
|
114
125
|
title String
|
|
115
|
-
description String?
|
|
126
|
+
description String? // optional notes/description for the "file"
|
|
116
127
|
ownerId String
|
|
117
|
-
owner User
|
|
118
|
-
icon String
|
|
119
|
-
color String
|
|
128
|
+
owner User @relation("UserWorkspaces", fields: [ownerId], references: [id], onDelete: Cascade)
|
|
129
|
+
icon String @default("📄")
|
|
130
|
+
color String @default("#9D00FF")
|
|
120
131
|
|
|
121
132
|
// A workspace (file) lives in a folder (nullable = root)
|
|
122
|
-
folderId
|
|
123
|
-
folder
|
|
124
|
-
|
|
125
|
-
channels Channel[]
|
|
133
|
+
folderId String?
|
|
134
|
+
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
126
135
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
channels Channel[]
|
|
137
|
+
|
|
138
|
+
sharedWith User[] @relation("WorkspaceSharedWith") // many-to-many for sharing (deprecated)
|
|
139
|
+
members WorkspaceMember[] // proper member management with roles
|
|
140
|
+
fileBeingAnalyzed Boolean @default(false)
|
|
130
141
|
|
|
131
142
|
analysisProgress Json?
|
|
132
143
|
|
|
133
144
|
needsAnalysis Boolean @default(false)
|
|
134
145
|
|
|
135
146
|
// Raw uploads attached to this workspace
|
|
136
|
-
uploads
|
|
147
|
+
uploads FileAsset[]
|
|
137
148
|
|
|
138
149
|
// AI outputs for this workspace (study guides, flashcards, etc.)
|
|
139
|
-
artifacts
|
|
140
|
-
|
|
150
|
+
artifacts Artifact[]
|
|
151
|
+
|
|
141
152
|
// Invitations
|
|
142
153
|
invitations WorkspaceInvitation[]
|
|
143
154
|
|
|
144
|
-
createdAt
|
|
145
|
-
updatedAt
|
|
155
|
+
createdAt DateTime @default(now())
|
|
156
|
+
updatedAt DateTime @updatedAt
|
|
146
157
|
|
|
147
158
|
@@index([ownerId, folderId])
|
|
148
159
|
}
|
|
@@ -152,25 +163,25 @@ model Channel {
|
|
|
152
163
|
workspaceId String
|
|
153
164
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
154
165
|
|
|
155
|
-
name
|
|
166
|
+
name String
|
|
156
167
|
|
|
157
|
-
createdAt
|
|
158
|
-
chats
|
|
168
|
+
createdAt DateTime @default(now())
|
|
169
|
+
chats Chat[]
|
|
159
170
|
|
|
160
171
|
@@index([workspaceId])
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
model Chat {
|
|
164
|
-
id
|
|
165
|
-
channelId
|
|
166
|
-
channel
|
|
175
|
+
id String @id @default(cuid())
|
|
176
|
+
channelId String
|
|
177
|
+
channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade)
|
|
167
178
|
|
|
168
|
-
user
|
|
169
|
-
userId
|
|
170
|
-
message
|
|
179
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
180
|
+
userId String?
|
|
181
|
+
message String // chat message content
|
|
171
182
|
|
|
172
|
-
updatedAt
|
|
173
|
-
createdAt
|
|
183
|
+
updatedAt DateTime @updatedAt
|
|
184
|
+
createdAt DateTime @default(now())
|
|
174
185
|
|
|
175
186
|
@@index([channelId, createdAt])
|
|
176
187
|
}
|
|
@@ -179,25 +190,26 @@ model Chat {
|
|
|
179
190
|
// User uploads (source materials for AI)
|
|
180
191
|
//
|
|
181
192
|
model FileAsset {
|
|
182
|
-
id
|
|
183
|
-
workspaceId String
|
|
184
|
-
workspace
|
|
193
|
+
id String @id @default(cuid())
|
|
194
|
+
workspaceId String?
|
|
195
|
+
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
185
196
|
|
|
186
|
-
userId
|
|
187
|
-
user
|
|
197
|
+
userId String?
|
|
198
|
+
user User? @relation("UserUploads", fields: [userId], references: [id], onDelete: Cascade)
|
|
188
199
|
|
|
189
|
-
name
|
|
190
|
-
mimeType
|
|
191
|
-
size
|
|
192
|
-
bucket
|
|
193
|
-
objectKey
|
|
194
|
-
url
|
|
195
|
-
checksum
|
|
196
|
-
aiTranscription
|
|
200
|
+
name String
|
|
201
|
+
mimeType String
|
|
202
|
+
size Int
|
|
203
|
+
bucket String?
|
|
204
|
+
objectKey String?
|
|
205
|
+
url String? // optional if serving via signed GET per-view
|
|
206
|
+
checksum String? // optional server-side integrity
|
|
207
|
+
aiTranscription Json? @default("{}")
|
|
197
208
|
|
|
198
|
-
meta
|
|
209
|
+
meta Json? // arbitrary metadata
|
|
199
210
|
|
|
200
211
|
createdAt DateTime @default(now())
|
|
212
|
+
User User[]
|
|
201
213
|
|
|
202
214
|
@@index([workspaceId])
|
|
203
215
|
@@index([userId, createdAt])
|
|
@@ -209,56 +221,56 @@ model FileAsset {
|
|
|
209
221
|
// - Some artifact types (flashcards, worksheet) have child rows
|
|
210
222
|
//
|
|
211
223
|
model Artifact {
|
|
212
|
-
id String
|
|
224
|
+
id String @id @default(cuid())
|
|
213
225
|
workspaceId String
|
|
214
|
-
workspace Workspace
|
|
226
|
+
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
215
227
|
|
|
216
|
-
type
|
|
217
|
-
title
|
|
218
|
-
isArchived
|
|
228
|
+
type ArtifactType
|
|
229
|
+
title String
|
|
230
|
+
isArchived Boolean @default(false)
|
|
219
231
|
|
|
220
|
-
generating
|
|
221
|
-
generatingMetadata Json?
|
|
232
|
+
generating Boolean @default(false)
|
|
233
|
+
generatingMetadata Json?
|
|
222
234
|
|
|
223
235
|
// Worksheet-specific fields
|
|
224
|
-
difficulty
|
|
225
|
-
estimatedTime String?
|
|
236
|
+
difficulty Difficulty? // only meaningful for WORKSHEET
|
|
237
|
+
estimatedTime String? // only meaningful for WORKSHEET
|
|
226
238
|
|
|
227
239
|
imageObjectKey String?
|
|
228
|
-
description
|
|
240
|
+
description String?
|
|
229
241
|
|
|
230
242
|
createdById String?
|
|
231
|
-
createdBy User?
|
|
243
|
+
createdBy User? @relation("UserArtifacts", fields: [createdById], references: [id], onDelete: SetNull)
|
|
232
244
|
|
|
233
|
-
versions
|
|
234
|
-
flashcards
|
|
235
|
-
questions
|
|
245
|
+
versions ArtifactVersion[] // text/transcript versions etc.
|
|
246
|
+
flashcards Flashcard[] // only meaningful for FLASHCARD_SET
|
|
247
|
+
questions WorksheetQuestion[] // only meaningful for WORKSHEET
|
|
236
248
|
podcastSegments PodcastSegment[] // only meaningful for PODCAST_EPISODE
|
|
237
249
|
|
|
238
|
-
createdAt
|
|
239
|
-
updatedAt
|
|
250
|
+
createdAt DateTime @default(now())
|
|
251
|
+
updatedAt DateTime @updatedAt
|
|
240
252
|
|
|
241
253
|
@@index([workspaceId, type])
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
model ArtifactVersion {
|
|
245
|
-
id
|
|
246
|
-
artifactId
|
|
247
|
-
artifact
|
|
257
|
+
id String @id @default(cuid())
|
|
258
|
+
artifactId String
|
|
259
|
+
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
248
260
|
|
|
249
261
|
// Plain text content (e.g., Study Guide body, Meeting Summary text, Podcast transcript)
|
|
250
|
-
content
|
|
262
|
+
content String // rich text serialized as markdown/HTML stored as TEXT
|
|
251
263
|
|
|
252
264
|
// For Podcast episodes or other media, store URLs / durations / etc. in data
|
|
253
|
-
data
|
|
265
|
+
data Json? // e.g., { "audioUrl": "...", "durationSec": 312, "voice": "..." }
|
|
254
266
|
|
|
255
267
|
// Version sequencing (auto-increment per artifact)
|
|
256
|
-
version
|
|
268
|
+
version Int
|
|
257
269
|
|
|
258
270
|
createdById String?
|
|
259
|
-
createdBy User?
|
|
271
|
+
createdBy User? @relation("UserArtifactVersions", fields: [createdById], references: [id], onDelete: SetNull)
|
|
260
272
|
|
|
261
|
-
createdAt
|
|
273
|
+
createdAt DateTime @default(now())
|
|
262
274
|
|
|
263
275
|
@@unique([artifactId, version]) // each artifact has 1,2,3...
|
|
264
276
|
@@index([artifactId])
|
|
@@ -272,15 +284,15 @@ model Flashcard {
|
|
|
272
284
|
artifactId String
|
|
273
285
|
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
274
286
|
|
|
275
|
-
front
|
|
276
|
-
back
|
|
277
|
-
tags
|
|
278
|
-
order
|
|
287
|
+
front String // question/term
|
|
288
|
+
back String // answer/definition
|
|
289
|
+
tags String[] // optional keywords
|
|
290
|
+
order Int @default(0)
|
|
279
291
|
|
|
280
292
|
// User progress tracking
|
|
281
|
-
progress
|
|
293
|
+
progress FlashcardProgress[]
|
|
282
294
|
|
|
283
|
-
createdAt
|
|
295
|
+
createdAt DateTime @default(now())
|
|
284
296
|
|
|
285
297
|
@@index([artifactId])
|
|
286
298
|
}
|
|
@@ -289,33 +301,33 @@ model Flashcard {
|
|
|
289
301
|
// User Progress on Flashcards (spaced repetition, mastery tracking)
|
|
290
302
|
//
|
|
291
303
|
model FlashcardProgress {
|
|
292
|
-
id
|
|
293
|
-
userId
|
|
294
|
-
user
|
|
295
|
-
|
|
304
|
+
id String @id @default(cuid())
|
|
305
|
+
userId String
|
|
306
|
+
user User @relation("UserFlashcardProgress", fields: [userId], references: [id], onDelete: Cascade)
|
|
307
|
+
|
|
296
308
|
flashcardId String
|
|
297
309
|
flashcard Flashcard @relation(fields: [flashcardId], references: [id], onDelete: Cascade)
|
|
298
310
|
|
|
299
311
|
// Study statistics
|
|
300
|
-
timesStudied Int
|
|
301
|
-
timesCorrect Int
|
|
302
|
-
timesIncorrect Int
|
|
303
|
-
timesIncorrectConsecutive Int
|
|
304
|
-
|
|
312
|
+
timesStudied Int @default(0)
|
|
313
|
+
timesCorrect Int @default(0)
|
|
314
|
+
timesIncorrect Int @default(0)
|
|
315
|
+
timesIncorrectConsecutive Int @default(0) // Track consecutive failures
|
|
316
|
+
|
|
305
317
|
// Spaced repetition data
|
|
306
|
-
easeFactor
|
|
307
|
-
interval
|
|
308
|
-
repetitions
|
|
309
|
-
|
|
318
|
+
easeFactor Float @default(2.5) // SM-2 algorithm ease factor
|
|
319
|
+
interval Int @default(0) // Days until next review
|
|
320
|
+
repetitions Int @default(0) // Consecutive correct answers
|
|
321
|
+
|
|
310
322
|
// Mastery level (0-100)
|
|
311
|
-
masteryLevel
|
|
312
|
-
|
|
323
|
+
masteryLevel Int @default(0)
|
|
324
|
+
|
|
313
325
|
// Timestamps
|
|
314
|
-
lastStudiedAt
|
|
315
|
-
nextReviewAt
|
|
316
|
-
|
|
317
|
-
createdAt
|
|
318
|
-
updatedAt
|
|
326
|
+
lastStudiedAt DateTime?
|
|
327
|
+
nextReviewAt DateTime?
|
|
328
|
+
|
|
329
|
+
createdAt DateTime @default(now())
|
|
330
|
+
updatedAt DateTime @updatedAt
|
|
319
331
|
|
|
320
332
|
@@unique([userId, flashcardId])
|
|
321
333
|
@@index([userId, nextReviewAt])
|
|
@@ -326,9 +338,9 @@ model FlashcardProgress {
|
|
|
326
338
|
// Worksheet Questions (child items of a WORKSHEET Artifact)
|
|
327
339
|
//
|
|
328
340
|
model WorksheetQuestion {
|
|
329
|
-
id String
|
|
341
|
+
id String @id @default(cuid())
|
|
330
342
|
artifactId String
|
|
331
|
-
artifact Artifact
|
|
343
|
+
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
332
344
|
|
|
333
345
|
prompt String
|
|
334
346
|
answer String?
|
|
@@ -336,35 +348,35 @@ model WorksheetQuestion {
|
|
|
336
348
|
difficulty Difficulty @default(MEDIUM)
|
|
337
349
|
order Int @default(0)
|
|
338
350
|
|
|
339
|
-
meta
|
|
351
|
+
meta Json? // e.g., { "choices": ["A","B","C","D"], "correct": 1, "options": [...] }
|
|
340
352
|
|
|
341
|
-
createdAt
|
|
353
|
+
createdAt DateTime @default(now())
|
|
354
|
+
progress WorksheetQuestionProgress[]
|
|
342
355
|
|
|
343
356
|
@@index([artifactId])
|
|
344
|
-
progress WorksheetQuestionProgress[]
|
|
345
357
|
}
|
|
346
358
|
|
|
347
359
|
//
|
|
348
360
|
// Per-user progress for Worksheet Questions
|
|
349
361
|
//
|
|
350
362
|
model WorksheetQuestionProgress {
|
|
351
|
-
id
|
|
352
|
-
worksheetQuestionId
|
|
353
|
-
worksheetQuestion
|
|
363
|
+
id String @id @default(cuid())
|
|
364
|
+
worksheetQuestionId String
|
|
365
|
+
worksheetQuestion WorksheetQuestion @relation(fields: [worksheetQuestionId], references: [id], onDelete: Cascade)
|
|
354
366
|
|
|
355
|
-
userId
|
|
356
|
-
user
|
|
367
|
+
userId String
|
|
368
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
357
369
|
|
|
358
|
-
modified
|
|
359
|
-
userAnswer
|
|
360
|
-
correct
|
|
361
|
-
completedAt
|
|
362
|
-
attempts
|
|
363
|
-
timeSpentSec
|
|
364
|
-
meta
|
|
370
|
+
modified Boolean @default(false)
|
|
371
|
+
userAnswer String?
|
|
372
|
+
correct Boolean? @default(false)
|
|
373
|
+
completedAt DateTime?
|
|
374
|
+
attempts Int @default(0)
|
|
375
|
+
timeSpentSec Int?
|
|
376
|
+
meta Json?
|
|
365
377
|
|
|
366
|
-
createdAt
|
|
367
|
-
updatedAt
|
|
378
|
+
createdAt DateTime @default(now())
|
|
379
|
+
updatedAt DateTime @updatedAt
|
|
368
380
|
|
|
369
381
|
@@unique([worksheetQuestionId, userId])
|
|
370
382
|
@@index([userId])
|
|
@@ -377,15 +389,15 @@ model WorkspaceMember {
|
|
|
377
389
|
id String @id @default(cuid())
|
|
378
390
|
workspaceId String
|
|
379
391
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
380
|
-
|
|
381
|
-
userId
|
|
382
|
-
user
|
|
383
|
-
|
|
384
|
-
role
|
|
385
|
-
|
|
386
|
-
joinedAt
|
|
387
|
-
updatedAt
|
|
388
|
-
|
|
392
|
+
|
|
393
|
+
userId String
|
|
394
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
395
|
+
|
|
396
|
+
role String @default("member") // "owner", "admin", "member"
|
|
397
|
+
|
|
398
|
+
joinedAt DateTime @default(now())
|
|
399
|
+
updatedAt DateTime @updatedAt
|
|
400
|
+
|
|
389
401
|
@@unique([workspaceId, userId]) // One membership per user per workspace
|
|
390
402
|
@@index([workspaceId])
|
|
391
403
|
@@index([userId])
|
|
@@ -398,20 +410,20 @@ model WorkspaceInvitation {
|
|
|
398
410
|
id String @id @default(cuid())
|
|
399
411
|
workspaceId String
|
|
400
412
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
401
|
-
|
|
402
|
-
email
|
|
403
|
-
role
|
|
404
|
-
token
|
|
405
|
-
|
|
413
|
+
|
|
414
|
+
email String
|
|
415
|
+
role String @default("member") // "owner", "admin", "member"
|
|
416
|
+
token String @unique @default(cuid()) // UUID for invitation link
|
|
417
|
+
|
|
406
418
|
invitedById String
|
|
407
|
-
invitedBy User
|
|
408
|
-
|
|
409
|
-
acceptedAt
|
|
410
|
-
expiresAt
|
|
411
|
-
|
|
412
|
-
createdAt
|
|
413
|
-
updatedAt
|
|
414
|
-
|
|
419
|
+
invitedBy User @relation("UserInvitations", fields: [invitedById], references: [id], onDelete: Cascade)
|
|
420
|
+
|
|
421
|
+
acceptedAt DateTime?
|
|
422
|
+
expiresAt DateTime @default(dbgenerated("NOW() + INTERVAL '7 days'"))
|
|
423
|
+
|
|
424
|
+
createdAt DateTime @default(now())
|
|
425
|
+
updatedAt DateTime @updatedAt
|
|
426
|
+
|
|
415
427
|
@@unique([workspaceId, email]) // One invitation per email per workspace
|
|
416
428
|
@@index([token])
|
|
417
429
|
@@index([workspaceId])
|
|
@@ -425,26 +437,26 @@ model PodcastSegment {
|
|
|
425
437
|
artifactId String
|
|
426
438
|
artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
|
|
427
439
|
|
|
428
|
-
title
|
|
429
|
-
content
|
|
430
|
-
startTime
|
|
431
|
-
duration
|
|
432
|
-
order
|
|
433
|
-
|
|
440
|
+
title String
|
|
441
|
+
content String // Full text content of the segment
|
|
442
|
+
startTime Int // Start time in seconds
|
|
443
|
+
duration Int // Duration in seconds
|
|
444
|
+
order Int // Display order within the episode
|
|
445
|
+
|
|
434
446
|
// Audio file reference
|
|
435
|
-
objectKey
|
|
436
|
-
audioUrl
|
|
437
|
-
|
|
447
|
+
objectKey String? // Google Cloud Storage object key
|
|
448
|
+
audioUrl String? // Cached signed URL (temporary)
|
|
449
|
+
|
|
438
450
|
// Metadata
|
|
439
|
-
keyPoints
|
|
440
|
-
meta
|
|
441
|
-
|
|
442
|
-
generating
|
|
451
|
+
keyPoints String[] // Array of key points
|
|
452
|
+
meta Json? // Additional metadata (voice settings, etc.)
|
|
453
|
+
|
|
454
|
+
generating Boolean @default(false)
|
|
443
455
|
generatingMetadata Json? // Additional metadata (voice settings, etc.)
|
|
444
|
-
|
|
445
|
-
createdAt
|
|
446
|
-
updatedAt
|
|
456
|
+
|
|
457
|
+
createdAt DateTime @default(now())
|
|
458
|
+
updatedAt DateTime @updatedAt
|
|
447
459
|
|
|
448
460
|
@@index([artifactId, order]) // For efficient ordering
|
|
449
461
|
@@index([artifactId, startTime]) // For time-based queries
|
|
450
|
-
}
|
|
462
|
+
}
|
package/src/lib/ai-session.ts
CHANGED
|
@@ -136,11 +136,6 @@ export class AISessionService {
|
|
|
136
136
|
fileType: 'image' | 'pdf',
|
|
137
137
|
maxPages?: number
|
|
138
138
|
): Promise<ProcessFileResult> {
|
|
139
|
-
const session = this.sessions.get(sessionId);
|
|
140
|
-
if (!session) {
|
|
141
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
139
|
await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
|
|
145
140
|
|
|
146
141
|
// Mock mode - return fake processing result
|
|
@@ -168,6 +163,8 @@ export class AISessionService {
|
|
|
168
163
|
formData.append('maxPages', maxPages.toString());
|
|
169
164
|
}
|
|
170
165
|
|
|
166
|
+
console.log('formData', formData);
|
|
167
|
+
|
|
171
168
|
// Retry logic for file processing
|
|
172
169
|
const maxRetries = 3;
|
|
173
170
|
let lastError: Error | null = null;
|
|
@@ -183,7 +180,7 @@ export class AISessionService {
|
|
|
183
180
|
const response = await fetch(AI_SERVICE_URL, {
|
|
184
181
|
method: 'POST',
|
|
185
182
|
body: formData,
|
|
186
|
-
signal: controller.signal,
|
|
183
|
+
// signal: controller.signal,
|
|
187
184
|
});
|
|
188
185
|
|
|
189
186
|
clearTimeout(timeoutId);
|
|
@@ -201,11 +198,6 @@ export class AISessionService {
|
|
|
201
198
|
throw new Error(result.error || 'File processing failed');
|
|
202
199
|
}
|
|
203
200
|
|
|
204
|
-
// Update session
|
|
205
|
-
session.files.push(fileUrl);
|
|
206
|
-
session.updatedAt = new Date();
|
|
207
|
-
this.sessions.set(sessionId, session);
|
|
208
|
-
|
|
209
201
|
return result as ProcessFileResult;
|
|
210
202
|
|
|
211
203
|
} catch (error) {
|
|
@@ -235,11 +227,6 @@ export class AISessionService {
|
|
|
235
227
|
|
|
236
228
|
// Generate study guide
|
|
237
229
|
async generateStudyGuide(sessionId: string, user: string): Promise<string> {
|
|
238
|
-
const session = this.sessions.get(sessionId);
|
|
239
|
-
if (!session) {
|
|
240
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
230
|
await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
|
|
244
231
|
// Mock mode - return fake study guide
|
|
245
232
|
if (MOCK_MODE) {
|
|
@@ -291,11 +278,6 @@ This mock study guide demonstrates the structure and format that would be genera
|
|
|
291
278
|
|
|
292
279
|
// Generate flashcard questions
|
|
293
280
|
async generateFlashcardQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string> {
|
|
294
|
-
// const session = this.sessions.get(sessionId);
|
|
295
|
-
// if (!session) {
|
|
296
|
-
// throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
|
|
297
|
-
// }
|
|
298
|
-
|
|
299
281
|
await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
|
|
300
282
|
// Mock mode - return fake flashcard questions
|
|
301
283
|
if (MOCK_MODE) {
|