Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix stop button preventing future chats and improve message order (Issue #67) #87

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 99 additions & 61 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,77 +11,115 @@ export const maxDuration = 60;

const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export async function POST(req: Request) {
const body = await req.json();
console.log("request body:", JSON.stringify(body, null, 2));
const threadId = body.threadId as Id<"threads">;
function ensureValidMessageOrder(messages: any[]) {
const validatedMessages = [];
let lastRole = null;

for (const message of messages) {
if (!message || typeof message !== 'object') {
console.error('Invalid message:', message);
continue;
}

if (!threadId) {
console.error("Invalid threadId");
return new Response('Invalid threadId', { status: 400 });
if (message.role === 'tool') {
// Always include tool messages
validatedMessages.push(message);
// After a tool message, we need an assistant message
if (lastRole !== 'assistant') {
validatedMessages.push({ role: 'assistant', content: 'Continuing the conversation based on the tool result.' });
}
} else if (message.role === 'assistant' && message.toolInvocations) {
// Handle assistant messages with tool invocations
validatedMessages.push(message);
// Add a tool message for each tool invocation
message.toolInvocations.forEach((invocation: any) => {
validatedMessages.push({
role: 'tool',
content: [{ type: 'tool-result', ...invocation }]
});
});
} else if (message.role === lastRole) {
// Combine consecutive messages with the same role
const lastMessage = validatedMessages[validatedMessages.length - 1];
if (lastMessage && typeof lastMessage.content === 'string' && typeof message.content === 'string') {
lastMessage.content += '\n' + message.content;
} else if (lastMessage && Array.isArray(lastMessage.content) && Array.isArray(message.content)) {
lastMessage.content = lastMessage.content.concat(message.content);
} else {
validatedMessages.push(message);
}
} else {
if (message.role === 'user' && lastRole === 'user') {
// If we have consecutive user messages, add an assistant message in between
validatedMessages.push({ role: 'assistant', content: 'I understand. Please continue.' });
}
validatedMessages.push(message);
}
lastRole = message.role;
}

const toolContext = await getToolContext(body);
const tools = getTools(toolContext, body.tools);
const { userId } = auth();
if (!userId) {
console.error("Unauthorized: No userId found");
return new Response('Unauthorized', { status: 401 });
// Ensure the conversation ends with a user message
if (validatedMessages.length === 0 || validatedMessages[validatedMessages.length - 1].role !== 'user') {
validatedMessages.push({ role: 'user', content: 'Continue' });
}

// Check user balance is > 0, but skip for GPT-4o Mini
if (toolContext.model.modelId !== 'gpt-4o-mini') {
try {
const userBalance = await convex.query(api.users.getUserBalance.getUserBalance, { clerk_user_id: userId });
if (userBalance <= 0) {
console.error("Insufficient credits for user:", userId);
return new Response('Insufficient credits', { status: 403 });
}
} catch (error) {
console.error('Error checking user balance:', error);
return new Response('Error checking user balance', { status: 500 });
}
// Ensure the conversation starts with a user message
if (validatedMessages.length === 0 || validatedMessages[0].role !== 'user') {
validatedMessages.unshift({ role: 'user', content: 'Start the conversation' });
}

console.log("BEFORE CONVERSION:", JSON.stringify(body.messages, null, 2));
const messages = convertToCoreMessages(body.messages);
console.log("AFTER CONVERSION:", JSON.stringify(messages, null, 2));
console.log("Converting messages to core messages");
const result = await streamText({
messages,
model: toolContext.model,
system: getSystemPrompt(toolContext),
tools,
});
return validatedMessages;
}

// console.log("Stream text result:", JSON.stringify(result, null, 2));
export async function POST(req: Request) {
try {
const body = await req.json();
console.log("Request body:", JSON.stringify(body, null, 2));
const threadId = body.threadId as Id<"threads">;

// // Save message and update user balance
// try {
// const usage = {
// promptTokens: result.usage?.promptTokens || 0,
// completionTokens: result.usage?.completionTokens || 0,
// totalTokens: result.usage?.totalTokens || 0,
// };
// console.log("Usage:", JSON.stringify(usage));
if (!threadId) {
console.error("Invalid threadId");
return new Response('Invalid threadId', { status: 400 });
}

// console.log("Calling saveMessageAndUpdateBalance with:", JSON.stringify({
// clerk_user_id: userId,
// model_id: toolContext.model,
// usage,
// }));
const toolContext = await getToolContext(body);
const tools = getTools(toolContext, body.tools);
const { userId } = auth();
if (!userId) {
console.error("Unauthorized: No userId found");
return new Response('Unauthorized', { status: 401 });
}

// const { cost_in_cents, newBalance } = await convex.mutation(api.users.saveMessageAndUpdateBalance, {
// clerk_user_id: userId,
// model_id: toolContext.model,
// usage,
// });
// Check user balance is > 0, but skip for GPT-4o Mini
if (toolContext.model.modelId !== 'gpt-4o-mini') {
try {
const userBalance = await convex.query(api.users.getUserBalance.getUserBalance, { clerk_user_id: userId });
if (userBalance <= 0) {
console.error("Insufficient credits for user:", userId);
return new Response('Insufficient credits', { status: 403 });
}
} catch (error) {
console.error('Error checking user balance:', error);
return new Response('Error checking user balance', { status: 500 });
}
}

// console.log(`Message cost: ${cost_in_cents} cents. New balance: ${newBalance}`);
// } catch (error) {
// console.error('Error saving message and updating user balance:', error);
// // console.error('Error details:', JSON.stringify(error, Object.getOwnPropertyNames(error)));
// }
console.log("BEFORE CONVERSION:", JSON.stringify(body.messages, null, 2));
const validatedMessages = ensureValidMessageOrder(body.messages);
console.log("AFTER VALIDATION:", JSON.stringify(validatedMessages, null, 2));
const messages = convertToCoreMessages(validatedMessages);
console.log("AFTER CONVERSION:", JSON.stringify(messages, null, 2));

const result = await streamText({
messages,
model: toolContext.model,
system: getSystemPrompt(toolContext),
tools,
});

return result.toAIStreamResponse();
}
return result.toAIStreamResponse();
} catch (error) {
console.error('Error in POST function:', error);
return new Response('Internal Server Error', { status: 500 });
}
}
Loading