Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ SMITHERY_API_KEY="your_smithery_api_key_here"
# NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is already used by mapbox-map.tsx
# Ensure it's also in your .env.local file if you haven't set it up yet.
# NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN="your_mapbox_public_token_here"

# Supabase Credentials
NEXT_PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL_HERE"
NEXT_PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY_HERE"
SUPABASE_SERVICE_ROLE_KEY="YOUR_SUPABASE_SERVICE_ROLE_KEY_HERE"
DATABASE_URL="postgresql://postgres:[YOUR-POSTGRES-PASSWORD]@[YOUR-SUPABASE-DB-HOST]:[PORT]/postgres"
27 changes: 21 additions & 6 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ export const AI = createAI<AIState, UIState>({
onGetUIState: async () => {
'use server';

// TODO: This needs to be adapted to use server-side auth if needed for initial UI state based on user.
// For now, it only uses getAIState().
const aiState = getAIState() as AIState;
if (aiState) {
const uiState = getUIStateFromAIState(aiState);
Expand All @@ -290,13 +292,13 @@ export const AI = createAI<AIState, UIState>({

const { chatId, messages } = state;
const createdAt = new Date();
const userId = 'anonymous';
// const userId = 'anonymous'; // Replaced with actual user ID
const path = `/search/${chatId}`;
const title =
messages.length > 0
? JSON.parse(messages[0].content)?.input?.substring(0, 100) ||
'Untitled'
: 'Untitled';
'Untitled Chat' // Default title consistency
: 'Untitled Chat';
// Add an 'end' message at the end to determine if the history needs to be reloaded
const updatedMessages: AIMessage[] = [
...messages,
Expand All @@ -308,15 +310,28 @@ export const AI = createAI<AIState, UIState>({
},
];

const chat: Chat = {

// Get the actual user ID using server-side auth
const { getCurrentUserIdOnServer } = await import('@/lib/auth/get-current-user');
const actualUserId = await getCurrentUserIdOnServer();

if (!actualUserId) {
console.error("onSetAIState: User not authenticated. Chat not saved.");
// Optionally, clear the AI state or handle appropriately
// For now, we just won't save if there's no user.
// Or, if chats for anonymous users are allowed with a guest ID, that logic would go here.
return;
}

const chat: Chat = { // Chat is OldChatType from @/lib/types
id: chatId,
createdAt,
userId,
userId: actualUserId, // Use the authenticated user's ID
path,
title,
messages: updatedMessages,
};
await saveChat(chat);
await saveChat(chat, actualUserId); // Pass actualUserId to saveChat
},
});

Expand Down
68 changes: 68 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { NextResponse, NextRequest } from 'next/server';
import { saveChat, createMessage, NewChat, NewMessage } from '@/lib/actions/chat-db';
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';
// import { generateUUID } from '@/lib/utils'; // Assuming generateUUID is in lib/utils as per PR context - not needed for PKs

// This is a simplified POST handler. PR #533's version might be more complex,
// potentially handling streaming AI responses and then saving.
// For now, this focuses on the database interaction part.
export async function POST(request: NextRequest) {
try {
const userId = await getCurrentUserIdOnServer();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const body = await request.json();

// Example: Distinguish between creating a new chat vs. adding a message to existing chat
// The actual structure of `body` would depend on client-side implementation.
// Let's assume a simple case: creating a new chat with an initial message.
const { title, initialMessageContent, role = 'user' } = body;

if (!initialMessageContent) {
return NextResponse.json({ error: 'Initial message content is required' }, { status: 400 });
}

const newChatData: NewChat = {
// id: generateUUID(), // Drizzle schema now has defaultRandom for UUIDs
userId: userId,
title: title || 'New Chat', // Default title if not provided
// createdAt: new Date(), // Handled by defaultNow() in schema
visibility: 'private', // Default visibility
};

// Use a transaction if creating chat and first message together
// For simplicity here, let's assume saveChat handles chat creation and returns ID, then we create a message.
// A more robust `saveChat` might create the chat and first message in one go.
// The `saveChat` in chat-db.ts is designed to handle this.

const firstMessage: Omit<NewMessage, 'chatId'> = {
// id: generateUUID(), // Drizzle schema now has defaultRandom for UUIDs
// chatId is omitted as it will be set by saveChat
userId: userId,
role: role as NewMessage['role'], // Ensure role type matches schema expectation
content: initialMessageContent,
// createdAt: new Date(), // Handled by defaultNow() in schema, not strictly needed here
};

// The saveChat in chat-db.ts is designed to take initial messages.
const savedChatId = await saveChat(newChatData, [firstMessage]);

if (!savedChatId) {
return NextResponse.json({ error: 'Failed to save chat' }, { status: 500 });
}

// Fetch the newly created chat and message to return (optional, but good for client)
// For now, just return success and the new chat ID.
return NextResponse.json({ message: 'Chat created successfully', chatId: savedChatId }, { status: 201 });

} catch (error) {
console.error('Error in POST /api/chat:', error);
let errorMessage = 'Internal Server Error';
if (error instanceof Error) {
errorMessage = error.message;
}
return NextResponse.json({ error: errorMessage }, { status: 500 });
}
}
37 changes: 37 additions & 0 deletions app/api/chats/all/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Content for app/api/chats/all/route.ts
import { NextResponse } from 'next/server';
import { clearHistory as dbClearHistory } from '@/lib/actions/chat-db';
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';
import { revalidatePath } from 'next/cache'; // For revalidating after clearing

export async function DELETE() {
try {
const userId = await getCurrentUserIdOnServer();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const success = await dbClearHistory(userId);
if (success) {
revalidatePath('/'); // Revalidate home or relevant pages
revalidatePath('/search'); // Revalidate search path
return NextResponse.json({ message: 'History cleared successfully' }, { status: 200 });
} else {
// This case might be redundant if dbClearHistory throws an error on failure,
// but kept for explicitness if it returns false for "no error but nothing done".
return NextResponse.json({ error: 'Failed to clear history' }, { status: 500 });
}
} catch (error) {
console.error('Error clearing history via API:', error);
let errorMessage = 'Internal Server Error clearing history';
if (error instanceof Error && error.message) {
// Use the error message from dbClearHistory if available (e.g., "User ID is required")
// This depends on dbClearHistory actually throwing or returning specific error messages.
// The current dbClearHistory in chat.ts returns {error: ...} which won't be caught here as an Error instance directly.
// However, the dbClearHistory in chat-db.ts returns boolean.
// Let's assume if dbClearHistory from chat-db.ts (which returns boolean) fails, it's a generic 500.
// If it were to throw, that would be caught.
}
Comment on lines +20 to +34
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove dead code and clarify error handling.

The comments and conditional logic in the error handling section appear to be leftover from development and should be cleaned up.

     } else {
-      // This case might be redundant if dbClearHistory throws an error on failure,
-      // but kept for explicitness if it returns false for "no error but nothing done".
       return NextResponse.json({ error: 'Failed to clear history' }, { status: 500 });
     }
   } catch (error) {
     console.error('Error clearing history via API:', error);
-    let errorMessage = 'Internal Server Error clearing history';
-    if (error instanceof Error && error.message) {
-        // Use the error message from dbClearHistory if available (e.g., "User ID is required")
-        // This depends on dbClearHistory actually throwing or returning specific error messages.
-        // The current dbClearHistory in chat.ts returns {error: ...} which won't be caught here as an Error instance directly.
-        // However, the dbClearHistory in chat-db.ts returns boolean.
-        // Let's assume if dbClearHistory from chat-db.ts (which returns boolean) fails, it's a generic 500.
-        // If it were to throw, that would be caught.
-    }
-    return NextResponse.json({ error: errorMessage }, { status: 500 });
+    return NextResponse.json({ error: 'Internal Server Error clearing history' }, { status: 500 });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// This case might be redundant if dbClearHistory throws an error on failure,
// but kept for explicitness if it returns false for "no error but nothing done".
return NextResponse.json({ error: 'Failed to clear history' }, { status: 500 });
}
} catch (error) {
console.error('Error clearing history via API:', error);
let errorMessage = 'Internal Server Error clearing history';
if (error instanceof Error && error.message) {
// Use the error message from dbClearHistory if available (e.g., "User ID is required")
// This depends on dbClearHistory actually throwing or returning specific error messages.
// The current dbClearHistory in chat.ts returns {error: ...} which won't be caught here as an Error instance directly.
// However, the dbClearHistory in chat-db.ts returns boolean.
// Let's assume if dbClearHistory from chat-db.ts (which returns boolean) fails, it's a generic 500.
// If it were to throw, that would be caught.
}
} else {
return NextResponse.json({ error: 'Failed to clear history' }, { status: 500 });
}
} catch (error) {
console.error('Error clearing history via API:', error);
return NextResponse.json({ error: 'Internal Server Error clearing history' }, { status: 500 });
}
🤖 Prompt for AI Agents
In app/api/chats/all/route.ts between lines 20 and 34, the error handling code
contains commented-out explanations and conditional checks that are no longer
relevant or used. Remove these dead comments and any unused conditional logic to
clean up and clarify the error handling section, leaving only the necessary code
that logs the error and returns the appropriate response.

return NextResponse.json({ error: errorMessage }, { status: 500 });
}
}
34 changes: 34 additions & 0 deletions app/api/chats/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextResponse, NextRequest } from 'next/server';
import { getChatsPage } from '@/lib/actions/chat-db';
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';

export async function GET(request: NextRequest) {
try {
const userId = await getCurrentUserIdOnServer();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const { searchParams } = new URL(request.url);

const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 100;
const DEFAULT_OFFSET = 0;

let limit = parseInt(searchParams.get('limit') || '', 10);
if (isNaN(limit) || limit < 1 || limit > MAX_LIMIT) {
limit = DEFAULT_LIMIT;
}

let offset = parseInt(searchParams.get('offset') || '', 10);
if (isNaN(offset) || offset < 0) {
offset = DEFAULT_OFFSET;
}

const result = await getChatsPage(userId, limit, offset);
return NextResponse.json(result);
} catch (error) {
console.error('Error fetching chats:', error);
return NextResponse.json({ error: 'Internal Server Error fetching chats' }, { status: 500 });
}
}
52 changes: 41 additions & 11 deletions app/search/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,71 @@
import { notFound, redirect } from 'next/navigation';
import { Chat } from '@/components/chat';
import { getChat } from '@/lib/actions/chat';
import { getChat, getChatMessages } from '@/lib/actions/chat'; // Added getChatMessages
import { AI } from '@/app/actions';
import { MapDataProvider } from '@/components/map/map-data-context';
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'; // For server-side auth
import type { AIMessage } from '@/lib/types'; // For AIMessage type
import type { Message as DrizzleMessage } from '@/lib/actions/chat-db'; // For DrizzleMessage type

export const maxDuration = 60;

export interface SearchPageProps {
params: Promise<{ id: string }>; // Change to Promise
params: Promise<{ id: string }>; // Keep as is for now
}

export async function generateMetadata({ params }: SearchPageProps) {
const { id } = await params; // Unwrap the Promise
const chat = await getChat(id, 'anonymous');
const { id } = await params; // Keep as is for now
// TODO: Metadata generation might need authenticated user if chats are private
// For now, assuming getChat can be called or it handles anon access for metadata appropriately
const userId = await getCurrentUserIdOnServer(); // Attempt to get user for metadata
const chat = await getChat(id, userId || 'anonymous'); // Pass userId or 'anonymous' if none
return {
title: chat?.title.toString().slice(0, 50) || 'Search',
title: chat?.title?.toString().slice(0, 50) || 'Search',
};
}

export default async function SearchPage({ params }: SearchPageProps) {
const { id } = await params; // Unwrap the Promise
const userId = 'anonymous';
const chat = await getChat(id, userId);
const { id } = await params; // Keep as is for now
const userId = await getCurrentUserIdOnServer();

if (!chat) {
if (!userId) {
// If no user, redirect to login or show appropriate page
// For now, redirecting to home, but a login page would be better.
redirect('/');
}

if (chat?.userId !== userId) {
const chat = await getChat(id, userId);

if (!chat) {
// If chat doesn't exist or user doesn't have access (handled by getChat)
notFound();
}

// Fetch messages for the chat
const dbMessages: DrizzleMessage[] = await getChatMessages(chat.id);

// Transform DrizzleMessages to AIMessages
const initialMessages: AIMessage[] = dbMessages.map((dbMsg): AIMessage => {
return {
id: dbMsg.id,
role: dbMsg.role as AIMessage['role'], // Cast role, ensure AIMessage['role'] includes all dbMsg.role possibilities
content: dbMsg.content,
createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined,
// 'type' and 'name' are not in the basic Drizzle 'messages' schema.
// These would be undefined unless specific logic is added to derive them.
// For instance, if a message with role 'tool' should have a 'name',
// or if some messages have a specific 'type' based on content or other flags.
// This mapping assumes standard user/assistant messages primarily.
};
});

return (
<AI
initialAIState={{
chatId: chat.id,
messages: chat.messages,
messages: initialMessages, // Use the transformed messages from the database
// isSharePage: true, // This was in PR#533, but share functionality is removed.
// If needed for styling or other logic, it can be set.
}}
>
<MapDataProvider>
Expand Down
44 changes: 0 additions & 44 deletions app/share/[id]/page.tsx

This file was deleted.

Binary file modified bun.lockb
Binary file not shown.
Loading