1+ import { eq , and } from 'drizzle-orm'
2+ import { type NextRequest , NextResponse } from 'next/server'
3+ import { z } from 'zod'
4+ import { getSession } from '@/lib/auth'
5+ import { createLogger } from '@/lib/logs/console-logger'
6+ import { db } from '@/db'
7+ import { copilotChats } from '@/db/schema'
8+ import { executeProviderRequest } from '@/providers'
9+ import { getRotatingApiKey } from '@/lib/utils'
10+
11+ const logger = createLogger ( 'CopilotChatAPI' )
12+
13+ const UpdateChatSchema = z . object ( {
14+ title : z . string ( ) . optional ( ) ,
15+ messages : z . array ( z . any ( ) ) . optional ( ) ,
16+ model : z . string ( ) . optional ( ) ,
17+ } )
18+
19+ const AddMessageSchema = z . object ( {
20+ message : z . object ( {
21+ role : z . enum ( [ 'user' , 'assistant' ] ) ,
22+ content : z . string ( ) ,
23+ timestamp : z . string ( ) . optional ( ) ,
24+ citations : z . array ( z . any ( ) ) . optional ( ) ,
25+ } ) ,
26+ } )
27+
28+ /**
29+ * GET /api/copilot/chats/[id]
30+ * Get a specific copilot chat
31+ */
32+ export async function GET ( req : NextRequest , { params } : { params : { id : string } } ) {
33+ try {
34+ const session = await getSession ( )
35+ if ( ! session ?. user ?. id ) {
36+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
37+ }
38+
39+ const chatId = params . id
40+
41+ logger . info ( `Getting chat ${ chatId } for user ${ session . user . id } ` )
42+
43+ const [ chat ] = await db
44+ . select ( )
45+ . from ( copilotChats )
46+ . where (
47+ and (
48+ eq ( copilotChats . id , chatId ) ,
49+ eq ( copilotChats . userId , session . user . id )
50+ )
51+ )
52+ . limit ( 1 )
53+
54+ if ( ! chat ) {
55+ return NextResponse . json ( { error : 'Chat not found' } , { status : 404 } )
56+ }
57+
58+ return NextResponse . json ( {
59+ success : true ,
60+ chat : {
61+ id : chat . id ,
62+ title : chat . title ,
63+ model : chat . model ,
64+ messages : chat . messages ,
65+ createdAt : chat . createdAt ,
66+ updatedAt : chat . updatedAt ,
67+ messageCount : Array . isArray ( chat . messages ) ? chat . messages . length : 0 ,
68+ } ,
69+ } )
70+ } catch ( error ) {
71+ logger . error ( 'Failed to get copilot chat:' , error )
72+ return NextResponse . json ( { error : 'Internal server error' } , { status : 500 } )
73+ }
74+ }
75+
76+ /**
77+ * PUT /api/copilot/chats/[id]
78+ * Update a copilot chat (add messages, update title, etc.)
79+ */
80+ export async function PUT ( req : NextRequest , { params } : { params : { id : string } } ) {
81+ try {
82+ const session = await getSession ( )
83+ if ( ! session ?. user ?. id ) {
84+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
85+ }
86+
87+ const chatId = params . id
88+ const body = await req . json ( )
89+ const { title, messages, model } = UpdateChatSchema . parse ( body )
90+
91+ logger . info ( `Updating chat ${ chatId } for user ${ session . user . id } ` )
92+
93+ // First verify the chat exists and belongs to the user
94+ const [ existingChat ] = await db
95+ . select ( )
96+ . from ( copilotChats )
97+ . where (
98+ and (
99+ eq ( copilotChats . id , chatId ) ,
100+ eq ( copilotChats . userId , session . user . id )
101+ )
102+ )
103+ . limit ( 1 )
104+
105+ if ( ! existingChat ) {
106+ return NextResponse . json ( { error : 'Chat not found' } , { status : 404 } )
107+ }
108+
109+ // Prepare update data
110+ const updateData : any = {
111+ updatedAt : new Date ( ) ,
112+ }
113+
114+ if ( title !== undefined ) updateData . title = title
115+ if ( messages !== undefined ) updateData . messages = messages
116+ if ( model !== undefined ) updateData . model = model
117+
118+ // Update the chat
119+ const [ updatedChat ] = await db
120+ . update ( copilotChats )
121+ . set ( updateData )
122+ . where ( eq ( copilotChats . id , chatId ) )
123+ . returning ( )
124+
125+ if ( ! updatedChat ) {
126+ throw new Error ( 'Failed to update chat' )
127+ }
128+
129+ logger . info ( `Updated chat ${ chatId } for user ${ session . user . id } ` )
130+
131+ return NextResponse . json ( {
132+ success : true ,
133+ chat : {
134+ id : updatedChat . id ,
135+ title : updatedChat . title ,
136+ model : updatedChat . model ,
137+ messages : updatedChat . messages ,
138+ createdAt : updatedChat . createdAt ,
139+ updatedAt : updatedChat . updatedAt ,
140+ messageCount : Array . isArray ( updatedChat . messages ) ? updatedChat . messages . length : 0 ,
141+ } ,
142+ } )
143+ } catch ( error ) {
144+ if ( error instanceof z . ZodError ) {
145+ return NextResponse . json (
146+ { error : 'Invalid request data' , details : error . errors } ,
147+ { status : 400 }
148+ )
149+ }
150+
151+ logger . error ( 'Failed to update copilot chat:' , error )
152+ return NextResponse . json ( { error : 'Internal server error' } , { status : 500 } )
153+ }
154+ }
155+
156+ /**
157+ * DELETE /api/copilot/chats/[id]
158+ * Delete a copilot chat
159+ */
160+ export async function DELETE ( req : NextRequest , { params } : { params : { id : string } } ) {
161+ try {
162+ const session = await getSession ( )
163+ if ( ! session ?. user ?. id ) {
164+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
165+ }
166+
167+ const chatId = params . id
168+
169+ logger . info ( `Deleting chat ${ chatId } for user ${ session . user . id } ` )
170+
171+ // First verify the chat exists and belongs to the user
172+ const [ existingChat ] = await db
173+ . select ( { id : copilotChats . id } )
174+ . from ( copilotChats )
175+ . where (
176+ and (
177+ eq ( copilotChats . id , chatId ) ,
178+ eq ( copilotChats . userId , session . user . id )
179+ )
180+ )
181+ . limit ( 1 )
182+
183+ if ( ! existingChat ) {
184+ return NextResponse . json ( { error : 'Chat not found' } , { status : 404 } )
185+ }
186+
187+ // Delete the chat
188+ await db
189+ . delete ( copilotChats )
190+ . where ( eq ( copilotChats . id , chatId ) )
191+
192+ logger . info ( `Deleted chat ${ chatId } for user ${ session . user . id } ` )
193+
194+ return NextResponse . json ( {
195+ success : true ,
196+ message : 'Chat deleted successfully' ,
197+ } )
198+ } catch ( error ) {
199+ logger . error ( 'Failed to delete copilot chat:' , error )
200+ return NextResponse . json ( { error : 'Internal server error' } , { status : 500 } )
201+ }
202+ }
203+
204+ /**
205+ * Generate a chat title using LLM based on the first user message
206+ */
207+ export async function generateChatTitle ( userMessage : string ) : Promise < string > {
208+ try {
209+ const apiKey = getRotatingApiKey ( 'anthropic' )
210+
211+ const response = await executeProviderRequest ( 'anthropic' , {
212+ model : 'claude-3-haiku-20240307' , // Use faster, cheaper model for title generation
213+ systemPrompt : 'You are a helpful assistant that generates concise, descriptive titles for chat conversations. Create a title that captures the main topic or question being discussed. Keep it under 50 characters and make it specific and clear.' ,
214+ context : `Generate a concise title for a conversation that starts with this user message: "${ userMessage } "
215+
216+ Return only the title text, nothing else.` ,
217+ temperature : 0.3 ,
218+ maxTokens : 50 ,
219+ apiKey,
220+ stream : false ,
221+ } )
222+
223+ // Handle different response types
224+ if ( typeof response === 'object' && 'content' in response ) {
225+ return response . content ?. trim ( ) || 'New Chat'
226+ }
227+
228+ return 'New Chat'
229+ } catch ( error ) {
230+ logger . error ( 'Failed to generate chat title:' , error )
231+ return 'New Chat' // Fallback title
232+ }
233+ }
0 commit comments