@@ -62,7 +62,7 @@ export const _isOwnerOfChat = async (chat: Chat, user: User | undefined): Promis
6262/**
6363 * Checks if a user has been explicitly shared access to a chat.
6464 */
65- export const _hasSharedAccess = async ( { prisma, chatId, userId} : { prisma : PrismaClient , chatId : string , userId : string | undefined } ) : Promise < boolean > => {
65+ export const _hasSharedAccess = async ( { prisma, chatId, userId } : { prisma : PrismaClient , chatId : string , userId : string | undefined } ) : Promise < boolean > => {
6666 if ( ! userId ) {
6767 return false ;
6868 }
@@ -79,6 +79,55 @@ export const _hasSharedAccess = async ({prisma, chatId, userId}: {prisma: Prisma
7979 return share !== null ;
8080} ;
8181
82+ export const _updateChatMessages = async ( { chatId, messages, prisma } : { chatId : string , messages : SBChatMessage [ ] , prisma : PrismaClient } ) => {
83+ await prisma . chat . update ( {
84+ where : {
85+ id : chatId ,
86+ } ,
87+ data : {
88+ messages : messages as unknown as Prisma . InputJsonValue ,
89+ } ,
90+ } ) ;
91+
92+ if ( env . DEBUG_WRITE_CHAT_MESSAGES_TO_FILE ) {
93+ const chatDir = path . join ( env . DATA_CACHE_DIR , 'chats' ) ;
94+ if ( ! fs . existsSync ( chatDir ) ) {
95+ fs . mkdirSync ( chatDir , { recursive : true } ) ;
96+ }
97+
98+ const chatFile = path . join ( chatDir , `${ chatId } .json` ) ;
99+ fs . writeFileSync ( chatFile , JSON . stringify ( messages , null , 2 ) ) ;
100+ }
101+ } ;
102+
103+
104+ export const _generateChatNameFromMessage = async ( { message, languageModelConfig } : { message : string , languageModelConfig : LanguageModel } ) => {
105+ const { model } = await _getAISDKLanguageModelAndOptions ( languageModelConfig ) ;
106+
107+ const prompt = `Convert this question into a short topic title (max 50 characters).
108+
109+ Rules:
110+ - Do NOT include question words (what, where, how, why, when, which)
111+ - Do NOT end with a question mark
112+ - Capitalize the first letter of the title
113+ - Focus on the subject/topic being discussed
114+ - Make it sound like a file name or category
115+
116+ Examples:
117+ "Where is the authentication code?" → "Authentication Code"
118+ "How to setup the database?" → "Database Setup"
119+ "What are the API endpoints?" → "API Endpoints"
120+
121+ User question: ${ message } ` ;
122+
123+ const result = await generateText ( {
124+ model,
125+ prompt,
126+ } ) ;
127+
128+ return result . text ;
129+ }
130+
82131export const createChat = async ( ) => sew ( ( ) =>
83132 withOptionalAuthV2 ( async ( { org, user, prisma } ) => {
84133 const isGuestUser = user === undefined ;
@@ -112,6 +161,11 @@ export const createChat = async () => sew(() =>
112161 } ) ;
113162 }
114163
164+ await captureEvent ( 'wa_chat_thread_created' , {
165+ chatId : chat . id ,
166+ isAnonymous : isGuestUser ,
167+ } ) ;
168+
115169 return {
116170 id : chat . id ,
117171 isAnonymous : isGuestUser ,
@@ -133,7 +187,7 @@ export const getChatInfo = async ({ chatId }: { chatId: string }) => sew(() =>
133187 }
134188
135189 const isOwner = await _isOwnerOfChat ( chat , user ) ;
136- const isSharedWithUser = await _hasSharedAccess ( { prisma, chatId, userId : user ?. id } ) ;
190+ const isSharedWithUser = await _hasSharedAccess ( { prisma, chatId, userId : user ?. id } ) ;
137191
138192 // Private chats can only be viewed by the owner or users it's been shared with
139193 if ( chat . visibility === ChatVisibility . PRIVATE && ! isOwner && ! isSharedWithUser ) {
@@ -170,24 +224,7 @@ export const updateChatMessages = async ({ chatId, messages }: { chatId: string,
170224 return notFound ( ) ;
171225 }
172226
173- await prisma . chat . update ( {
174- where : {
175- id : chatId ,
176- } ,
177- data : {
178- messages : messages as unknown as Prisma . InputJsonValue ,
179- } ,
180- } ) ;
181-
182- if ( env . DEBUG_WRITE_CHAT_MESSAGES_TO_FILE ) {
183- const chatDir = path . join ( env . DATA_CACHE_DIR , 'chats' ) ;
184- if ( ! fs . existsSync ( chatDir ) ) {
185- fs . mkdirSync ( chatDir , { recursive : true } ) ;
186- }
187-
188- const chatFile = path . join ( chatDir , `${ chatId } .json` ) ;
189- fs . writeFileSync ( chatFile , JSON . stringify ( messages , null , 2 ) ) ;
190- }
227+ await _updateChatMessages ( { chatId, messages, prisma } ) ;
191228
192229 return {
193230 success : true ,
@@ -295,54 +332,51 @@ export const updateChatVisibility = async ({ chatId, visibility }: { chatId: str
295332) ;
296333
297334export const generateAndUpdateChatNameFromMessage = async ( { chatId, languageModelId, message } : { chatId : string , languageModelId : string , message : string } ) => sew ( ( ) =>
298- withOptionalAuthV2 ( async ( ) => {
299- // From the language model ID, attempt to find the
300- // corresponding config in `config.json`.
301- const languageModelConfig =
302- ( await _getConfiguredLanguageModelsFull ( ) )
303- . find ( ( model ) => model . model === languageModelId ) ;
304-
305- if ( ! languageModelConfig ) {
306- return serviceErrorResponse ( {
307- statusCode : StatusCodes . BAD_REQUEST ,
308- errorCode : ErrorCode . INVALID_REQUEST_BODY ,
309- message : `Language model ${ languageModelId } is not configured.` ,
310- } ) ;
311- }
312-
313- const { model } = await _getAISDKLanguageModelAndOptions ( languageModelConfig ) ;
314-
315- const prompt = `Convert this question into a short topic title (max 50 characters).
335+ withOptionalAuthV2 ( async ( { prisma, user, org } ) => {
336+ const chat = await prisma . chat . findUnique ( {
337+ where : {
338+ id : chatId ,
339+ } ,
340+ } ) ;
316341
317- Rules:
318- - Do NOT include question words (what, where, how, why, when, which)
319- - Do NOT end with a question mark
320- - Capitalize the first letter of the title
321- - Focus on the subject/topic being discussed
322- - Make it sound like a file name or category
342+ if ( ! chat ) {
343+ return notFound ( ) ;
344+ }
323345
324- Examples:
325- "Where is the authentication code?" → "Authentication Code"
326- "How to setup the database?" → "Database Setup"
327- "What are the API endpoints?" → "API Endpoints"
346+ const isOwner = await _isOwnerOfChat ( chat , user ) ;
347+ if ( ! isOwner ) {
348+ return notFound ( ) ;
349+ }
328350
329- User question: ${ message } ` ;
351+ const languageModelConfig =
352+ ( await _getConfiguredLanguageModelsFull ( ) )
353+ . find ( ( model ) => model . model === languageModelId ) ;
330354
331- const result = await generateText ( {
332- model,
333- prompt,
355+ if ( ! languageModelConfig ) {
356+ return serviceErrorResponse ( {
357+ statusCode : StatusCodes . BAD_REQUEST ,
358+ errorCode : ErrorCode . INVALID_REQUEST_BODY ,
359+ message : `Language model ${ languageModelId } is not configured.` ,
334360 } ) ;
361+ }
335362
336- await updateChatName ( {
337- chatId,
338- name : result . text ,
339- } ) ;
363+ const name = await _generateChatNameFromMessage ( { message, languageModelConfig } ) ;
340364
341- return {
342- success : true ,
343- }
365+ await prisma . chat . update ( {
366+ where : {
367+ id : chatId ,
368+ orgId : org . id ,
369+ } ,
370+ data : {
371+ name : name ,
372+ } ,
344373 } )
345- )
374+
375+ return {
376+ success : true ,
377+ }
378+ } )
379+ )
346380
347381export const deleteChat = async ( { chatId } : { chatId : string } ) => sew ( ( ) =>
348382 withAuthV2 ( async ( { org, user, prisma } ) => {
@@ -436,7 +470,7 @@ export const duplicateChat = async ({ chatId, newName }: { chatId: string, newNa
436470
437471 // Check if user can access the chat (owner, shared, or public)
438472 const isOwner = await _isOwnerOfChat ( originalChat , user ) ;
439- const isSharedWithUser = await _hasSharedAccess ( { prisma, chatId, userId : user ?. id } ) ;
473+ const isSharedWithUser = await _hasSharedAccess ( { prisma, chatId, userId : user ?. id } ) ;
440474 if ( originalChat . visibility === ChatVisibility . PRIVATE && ! isOwner && ! isSharedWithUser ) {
441475 return notFound ( ) ;
442476 }
@@ -617,7 +651,7 @@ export const submitFeedback = async ({
617651 }
618652
619653 // When a chat is private, only the creator or shared users can submit feedback.
620- const isSharedWithUser = await _hasSharedAccess ( { prisma, chatId, userId : user ?. id } ) ;
654+ const isSharedWithUser = await _hasSharedAccess ( { prisma, chatId, userId : user ?. id } ) ;
621655 if ( chat . visibility === ChatVisibility . PRIVATE && chat . createdById !== user ?. id && ! isSharedWithUser ) {
622656 return notFound ( ) ;
623657 }
0 commit comments