@@ -21,47 +21,50 @@ const openaiRouter = express.Router()
21
21
const storage = multer . memoryStorage ( )
22
22
const upload = multer ( { storage } )
23
23
24
- const fileParsing = async ( options : any , req : any ) => {
24
+ const PostStreamSchemaV2 = z . object ( {
25
+ options : z . object ( {
26
+ model : z . string ( ) ,
27
+ assistantInstructions : z . string ( ) . optional ( ) ,
28
+ messages : z . array ( z . any ( ) ) ,
29
+ userConsent : z . boolean ( ) . optional ( ) ,
30
+ modelTemperature : z . number ( ) . min ( 0 ) . max ( 2 ) ,
31
+ saveConsent : z . boolean ( ) . optional ( ) ,
32
+ prevResponseId : z . string ( ) . optional ( ) ,
33
+ courseId : z . string ( ) . optional ( ) ,
34
+ ragIndexId : z . number ( ) . optional ( ) . nullable ( ) ,
35
+ } ) ,
36
+ courseId : z . string ( ) . optional ( ) ,
37
+ } )
38
+
39
+ type PostStreamBody = z . infer < typeof PostStreamSchemaV2 >
40
+
41
+ const parseFileAndAddToLastMessage = async ( options : PostStreamBody [ 'options' ] , file : Express . Multer . File ) => {
25
42
let fileContent = ''
26
43
27
44
const textFileTypes = [ 'text/plain' , 'text/html' , 'text/css' , 'text/csv' , 'text/markdown' , 'text/md' ]
28
- if ( textFileTypes . includes ( req . file . mimetype ) ) {
29
- const fileBuffer = req . file . buffer
45
+ if ( textFileTypes . includes ( file . mimetype ) ) {
46
+ const fileBuffer = file . buffer
30
47
fileContent = fileBuffer . toString ( 'utf8' )
31
48
}
32
49
33
- if ( req . file . mimetype === 'application/pdf' ) {
34
- fileContent = await pdfToText ( req . file . buffer )
50
+ if ( file . mimetype === 'application/pdf' ) {
51
+ fileContent = await pdfToText ( file . buffer )
35
52
}
36
53
37
- const allMessages = options . messages
54
+ const messageToAddFileTo = options . messages [ options . messages . length - 1 ]
38
55
39
56
const updatedMessage = {
40
- ...allMessages [ allMessages . length - 1 ] ,
41
- content : `${ allMessages [ allMessages . length - 1 ] . content } ${ fileContent } ` ,
57
+ ...messageToAddFileTo ,
58
+ content : `${ messageToAddFileTo . content } ${ fileContent } ` ,
42
59
}
43
- options . messages . pop ( )
44
60
61
+ // Remove the old message and add the new one
62
+ options . messages . pop ( )
45
63
options . messages = [ ...options . messages , updatedMessage ]
46
64
47
65
return options . messages
48
66
}
49
67
50
- const PostStreamSchemaV2 = z . object ( {
51
- options : z . object ( {
52
- model : z . string ( ) ,
53
- assistantInstructions : z . string ( ) . optional ( ) ,
54
- messages : z . array ( z . any ( ) ) ,
55
- userConsent : z . boolean ( ) . optional ( ) ,
56
- modelTemperature : z . number ( ) . min ( 0 ) . max ( 2 ) ,
57
- saveConsent : z . boolean ( ) . optional ( ) ,
58
- prevResponseId : z . string ( ) . optional ( ) ,
59
- courseId : z . string ( ) . optional ( ) ,
60
- ragIndexId : z . number ( ) . optional ( ) . nullable ( ) ,
61
- } ) ,
62
- courseId : z . string ( ) . optional ( ) ,
63
- } )
64
-
65
68
openaiRouter . post ( '/stream/v2' , upload . single ( 'file' ) , async ( r , res ) => {
66
69
const req = r as RequestWithUser
67
70
const { options, courseId } = PostStreamSchemaV2 . parse ( JSON . parse ( req . body . data ) )
@@ -82,7 +85,8 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
82
85
throw ApplicationError . NotFound ( 'Course not found' )
83
86
}
84
87
85
- const usageAllowed = ( courseId ? await checkCourseUsage ( user , courseId ) : model === FREE_MODEL ) || ( await checkUsage ( user , model ) )
88
+ const isFreeModel = model === FREE_MODEL
89
+ const usageAllowed = ( courseId ? await checkCourseUsage ( user , courseId ) : isFreeModel ) || ( await checkUsage ( user , model ) )
86
90
87
91
if ( ! usageAllowed ) {
88
92
throw ApplicationError . Forbidden ( 'Usage limit reached' )
@@ -107,7 +111,7 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
107
111
108
112
try {
109
113
if ( req . file ) {
110
- optionsMessagesWithFile = await fileParsing ( options , req )
114
+ optionsMessagesWithFile = await parseFileAndAddToLastMessage ( options , req )
111
115
}
112
116
} catch ( error ) {
113
117
logger . error ( 'Error parsing file' , { error } )
@@ -120,7 +124,7 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
120
124
let tokenCount = calculateUsage ( options as any , encoding )
121
125
const tokenUsagePercentage = Math . round ( ( tokenCount / DEFAULT_TOKEN_LIMIT ) * 100 )
122
126
123
- if ( options . model !== FREE_MODEL && tokenCount > 0.1 * DEFAULT_TOKEN_LIMIT ) {
127
+ if ( ! isFreeModel && tokenCount > 0.1 * DEFAULT_TOKEN_LIMIT ) {
124
128
res . status ( 201 ) . json ( {
125
129
tokenConsumtionWarning : true ,
126
130
message : `You are about to use ${ tokenUsagePercentage } % of your monthly CurreChat usage` ,
@@ -132,7 +136,6 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
132
136
const contextLimit = getModelContextLimit ( options . model )
133
137
134
138
if ( tokenCount > contextLimit ) {
135
- logger . info ( 'Maximum context reached' ) // @todo sure we need to log twice the error message?
136
139
throw ApplicationError . BadRequest ( 'Model maximum context reached' )
137
140
}
138
141
@@ -142,7 +145,6 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
142
145
143
146
if ( ragIndexId ) {
144
147
if ( ! courseId && ! user . isAdmin ) {
145
- logger . error ( 'User is not admin and trying to access non-course rag' )
146
148
throw ApplicationError . Forbidden ( 'User is not admin and trying to access non-course rag' )
147
149
}
148
150
@@ -172,6 +174,7 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
172
174
user,
173
175
} )
174
176
177
+ // Using the responses API, we only send the last message and the id to previous message
175
178
const latestMessage = options . messages [ options . messages . length - 1 ]
176
179
177
180
const events = await responsesClient . createResponse ( {
@@ -180,6 +183,7 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
180
183
include : ragIndexId ? [ 'file_search_call.results' ] : [ ] ,
181
184
} )
182
185
186
+ // Prepare for streaming response
183
187
res . setHeader ( 'content-type' , 'text/event-stream' )
184
188
185
189
const result = await responsesClient . handleResponse ( {
@@ -190,15 +194,19 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
190
194
191
195
tokenCount += result . tokenCount
192
196
193
- let userToCharge = user
194
- if ( inProduction && req . hijackedBy ) {
195
- userToCharge = req . hijackedBy
196
- }
197
+ // Increment user usage if not using free model
198
+ // If the user is hijacked by admin in production, charge the admin instead
199
+ if ( ! isFreeModel ) {
200
+ let userToCharge = user
201
+ if ( inProduction && req . hijackedBy ) {
202
+ userToCharge = req . hijackedBy
203
+ }
197
204
198
- if ( courseId && model !== FREE_MODEL && model !== 'mock' ) {
199
- await incrementCourseUsage ( userToCharge , courseId , tokenCount )
200
- } else if ( model !== FREE_MODEL && model !== 'mock' ) {
201
- await incrementUsage ( userToCharge , tokenCount )
205
+ if ( courseId ) {
206
+ await incrementCourseUsage ( userToCharge , courseId , tokenCount )
207
+ } else {
208
+ await incrementUsage ( userToCharge , tokenCount )
209
+ }
202
210
}
203
211
204
212
logger . info ( `Stream ended. Total tokens: ${ tokenCount } ` , {
@@ -208,10 +216,9 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
208
216
courseId,
209
217
} )
210
218
211
- const consentToSave = courseId && course ! . saveDiscussions && options . saveConsent
212
-
219
+ // If course has saveDiscussion turned on and user has consented to saving the discussion, save the discussion
220
+ const consentToSave = courseId && course ?. saveDiscussions && options . saveConsent
213
221
console . log ( `Consent to save discussion: ${ options . saveConsent } ${ user . username } ` )
214
-
215
222
if ( consentToSave ) {
216
223
// @todo : should file search results also be saved?
217
224
const discussion = {
@@ -261,7 +268,7 @@ openaiRouter.post('/stream', upload.single('file'), async (r, res) => {
261
268
262
269
try {
263
270
if ( req . file ) {
264
- optionsMessagesWithFile = await fileParsing ( options , req )
271
+ optionsMessagesWithFile = await parseFileAndAddToLastMessage ( options , req )
265
272
}
266
273
} catch ( error ) {
267
274
logger . error ( 'Error parsing file' , { error } )
0 commit comments