Skip to content
Open
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
27 changes: 21 additions & 6 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { inquire, researcher, taskManager, querySuggestor, resolutionSearch } fr
// The geospatialTool (if used by agents like researcher) now manages its own MCP client.
import { writer } from '@/lib/agents/writer'
import { saveChat, getSystemPrompt } from '@/lib/actions/chat' // Added getSystemPrompt
import { retrieveContext } from '@/lib/actions/rag'
import { Chat, AIMessage } from '@/lib/types'
import { UserMessage } from '@/components/user-message'
import { BotMessage } from '@/components/message'
Expand Down Expand Up @@ -284,6 +285,11 @@ async function submit(formData?: FormData, skip?: boolean) {
const userId = 'anonymous'
const currentSystemPrompt = (await getSystemPrompt(userId)) || ''

const retrievedContext = await retrieveContext(userInput, aiState.get().chatId)
const augmentedSystemPrompt = retrievedContext.length > 0
? `Context: ${retrievedContext.join('\n')}\n${currentSystemPrompt}`
: currentSystemPrompt

Comment on lines +288 to +292
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard retrieveContext when userInput is empty/undefined.

userInput can be falsy (e.g., file-only input or skip). Calling retrieveContext with undefined may throw or degrade results.

-  const retrievedContext = await retrieveContext(userInput, aiState.get().chatId)
+  const retrievedContext = userInput
+    ? await retrieveContext(userInput, aiState.get().chatId)
+    : []
📝 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
const retrievedContext = await retrieveContext(userInput, aiState.get().chatId)
const augmentedSystemPrompt = retrievedContext.length > 0
? `Context: ${retrievedContext.join('\n')}\n${currentSystemPrompt}`
: currentSystemPrompt
const retrievedContext = userInput
? await retrieveContext(userInput, aiState.get().chatId)
: []
const augmentedSystemPrompt = retrievedContext.length > 0
? `Context: ${retrievedContext.join('\n')}\n${currentSystemPrompt}`
: currentSystemPrompt
🤖 Prompt for AI Agents
In app/actions.tsx around lines 288 to 292, guard the call to retrieveContext so
it is only invoked when userInput is non-empty: if userInput is falsy, set
retrievedContext to an empty array (or directly set augmentedSystemPrompt =
currentSystemPrompt) instead of calling retrieveContext; otherwise call
retrieveContext(userInput, aiState.get().chatId) and build augmentedSystemPrompt
as before. Ensure types reflect that retrievedContext may be an empty array and
avoid passing undefined into retrieveContext.

async function processEvents() {
let action: any = { object: { next: 'proceed' } }
if (!skip) action = (await taskManager(messages)) ?? action
Expand Down Expand Up @@ -320,7 +326,7 @@ async function submit(formData?: FormData, skip?: boolean) {
: answer.length === 0 && !errorOccurred
) {
const { fullResponse, hasError, toolResponses } = await researcher(
currentSystemPrompt,
augmentedSystemPrompt,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Inconsistent system prompt usage breaks RAG integration.

Line 329 correctly uses augmentedSystemPrompt in the researcher call, but line 373 uses currentSystemPrompt in the writer call. This means the writer agent won't receive the retrieved context, breaking the RAG workflow for the useSpecificAPI path.

Apply this diff to fix the inconsistency:

     const latestMessages = modifiedMessages.slice(maxMessages * -1)
     answer = await writer(
-      currentSystemPrompt,
+      augmentedSystemPrompt,
       uiStream,
       streamText,
       latestMessages
     )

Also applies to: 372-377

🤖 Prompt for AI Agents
In app/actions.tsx around lines 329 and 372-377, the researcher call correctly
passes augmentedSystemPrompt but the writer call uses currentSystemPrompt, which
prevents the retrieved RAG context from being passed to the writer; update the
writer agent invocation(s) in that useSpecificAPI code path to pass
augmentedSystemPrompt (same variable used for the researcher) instead of
currentSystemPrompt so both researcher and writer receive the
retrieved/context-augmented system prompt. Ensure any other writer calls in that
block are similarly updated to use augmentedSystemPrompt.

uiStream,
streamText,
messages,
Expand Down Expand Up @@ -565,14 +571,23 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
// New messages will store the content array or string directly
messageContent = content
}
const location = (message as any).locations
return {
id,
component: (
<UserMessage
content={messageContent}
chatId={chatId}
showShare={index === 0 && !isSharePage}
/>
<>
<UserMessage
content={messageContent}
chatId={chatId}
showShare={index === 0 && !isSharePage}
/>
{location && (
<GeoJsonLayer
id={`${id}-geojson`}
data={location.geojson}
/>
)}
</>
Comment on lines +585 to +590

Choose a reason for hiding this comment

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

GeoJsonLayer is used in JSX but not imported anywhere in this file. This will throw at runtime (and likely at compile time). Please import the component or remove the render until it’s properly wired up.

Suggestion

Add a proper import for the layer component (adjust the path to your actual component), and guard against missing location.geojson:

import { GeoJsonLayer } from '@/components/map/geojson-layer'
...
{location?.geojson && (
  <GeoJsonLayer id={`${id}-geojson`} data={location.geojson} />
)}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion

Comment on lines +574 to +590
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix: Scope the location variable to prevent switch case leakage.

The location variable declared in line 574 can be accessed by other switch cases, which is error-prone. The static analysis tool correctly flags this.

Apply this diff to wrap the declaration in a block:

         case 'user':
           switch (type) {
             case 'input':
-            case 'input_related':
+            case 'input_related': {
               let messageContent: string | any[]
               try {
                 // For backward compatibility with old messages that stored a JSON string
                 const json = JSON.parse(content as string)
                 messageContent =
                   type === 'input' ? json.input : json.related_query
               } catch (e) {
                 // New messages will store the content array or string directly
                 messageContent = content
               }
               const location = (message as any).locations
               return {
                 id,
                 component: (
                   <>
                     <UserMessage
                       content={messageContent}
                       chatId={chatId}
                       showShare={index === 0 && !isSharePage}
                     />
                     {location && (
                       <GeoJsonLayer
                         id={`${id}-geojson`}
                         data={location.geojson}
                       />
                     )}
                   </>
                 )
               }
+            }
             case 'inquiry':
📝 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
const location = (message as any).locations
return {
id,
component: (
<UserMessage
content={messageContent}
chatId={chatId}
showShare={index === 0 && !isSharePage}
/>
<>
<UserMessage
content={messageContent}
chatId={chatId}
showShare={index === 0 && !isSharePage}
/>
{location && (
<GeoJsonLayer
id={`${id}-geojson`}
data={location.geojson}
/>
)}
</>
case 'user':
switch (type) {
case 'input':
case 'input_related': {
let messageContent: string | any[]
try {
// For backward compatibility with old messages that stored a JSON string
const json = JSON.parse(content as string)
messageContent =
type === 'input' ? json.input : json.related_query
} catch (e) {
// New messages will store the content array or string directly
messageContent = content
}
const location = (message as any).locations
return {
id,
component: (
<>
<UserMessage
content={messageContent}
chatId={chatId}
showShare={index === 0 && !isSharePage}
/>
{location && (
<GeoJsonLayer
id={`${id}-geojson`}
data={location.geojson}
/>
)}
</>
)
}
}
case 'inquiry':
// …
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 574-574: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In app/actions.tsx around lines 574 to 590, the variable "location" is declared
at function/switch scope causing leakage into other switch cases; wrap the JSX
branch that uses it in its own block (e.g., surround the code that declares and
uses location with { /* block */ { const location = (message as any).locations;
return { ... } } } ) so the "location" const is lexically scoped to that branch
only, preventing accidental access from other cases.

)
}
case 'inquiry':
Expand Down
52 changes: 19 additions & 33 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { NextResponse, NextRequest } from 'next/server';
import { saveChat, createMessage, NewChat, NewMessage } from '@/lib/actions/chat-db';
import { saveChat } from '@/lib/actions/chat';
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
import { type Chat } from '@/lib/types';
import { v4 as uuidv4 } from 'uuid';

// 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();
Expand All @@ -14,47 +12,35 @@ export async function POST(request: NextRequest) {
}

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;
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
const newChat: Chat = {
id: uuidv4(),
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
title: title || 'New Chat',
createdAt: new Date(),
path: '',
messages: [
{
id: uuidv4(),
role: role,
content: initialMessageContent,
createdAt: new Date(),
}
]
};
Comment on lines +22 to 36
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Initialize path to the search URL to avoid empty links.

path: '' can break shareable links downstream. Set it up-front.

-    const newChat: Chat = {
+    const newChat: Chat = {
       id: uuidv4(),
       userId: userId,
       title: title || 'New Chat',
       createdAt: new Date(),
-      path: '',
+      path: `/search/${/* chat id */ ''}`, // set after id is known
       messages: [

Or compute after the id is set:

-    const newChat: Chat = {
-      id: uuidv4(),
+    const chatId = uuidv4()
+    const newChat: Chat = {
+      id: chatId,
       userId: userId,
       title: title || 'New Chat',
       createdAt: new Date(),
-      path: '',
+      path: `/search/${chatId}`,
       messages: [
📝 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
const newChat: Chat = {
id: uuidv4(),
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
title: title || 'New Chat',
createdAt: new Date(),
path: '',
messages: [
{
id: uuidv4(),
role: role,
content: initialMessageContent,
createdAt: new Date(),
}
]
};
const chatId = uuidv4();
const newChat: Chat = {
id: chatId,
userId: userId,
title: title || 'New Chat',
createdAt: new Date(),
path: `/search/${chatId}`,
messages: [
{
id: uuidv4(),
role: role,
content: initialMessageContent,
createdAt: new Date(),
}
]
};
🤖 Prompt for AI Agents
In app/api/chat/route.ts around lines 22 to 36, the new Chat object sets path:
'' which can create broken shareable links; after generating the chat id,
populate path with the proper search URL (for example using the generated id,
e.g. `/search/${id}`) or assign path after constructing the id (compute path =
`/search/${newChat.id}`) so the created chat always contains a valid shareable
URL.


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

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) {
Expand Down
26 changes: 8 additions & 18 deletions app/api/chats/all/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Content for app/api/chats/all/route.ts
import { NextResponse } from 'next/server';
import { clearHistory as dbClearHistory } from '@/lib/actions/chat-db';
import { clearChats } from '@/lib/actions/chat';
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';
import { revalidatePath } from 'next/cache'; // For revalidating after clearing

export async function DELETE() {
try {
Expand All @@ -11,26 +9,18 @@ export async function DELETE() {
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 });
const result = await clearChats(userId);
if (result && 'error' in result) {
return NextResponse.json({ error: result.error }, { status: 500 });
}

return NextResponse.json({ message: 'History cleared successfully' }, { status: 200 });

Comment on lines +12 to +18

Choose a reason for hiding this comment

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

This API route will still succeed even if clearChats triggers a redirect (see related comment). Ensure the delete call returns a 200 JSON response to the client instead of a redirect payload.

Suggestion

After refactoring clearChats to not redirect by default, call it with await clearChats(userId, { redirectTo: null }) and keep returning the success JSON. Reply with "@CharlieHelps yes please" and I’ll update both the action and this call site.

} 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.
errorMessage = error.message
}
return NextResponse.json({ error: errorMessage }, { status: 500 });
}
Expand Down
22 changes: 3 additions & 19 deletions app/api/chats/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextResponse, NextRequest } from 'next/server';
import { getChatsPage } from '@/lib/actions/chat-db';
import { getChats } from '@/lib/actions/chat';
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';

export async function GET(request: NextRequest) {
Expand All @@ -9,24 +9,8 @@ export async function GET(request: NextRequest) {
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);
const chats = await getChats(userId);
return NextResponse.json({ chats });
Comment on lines +12 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Potential scalability issue: Removed pagination.

The endpoint now returns all chats at once instead of supporting pagination. For users with many chats (hundreds or thousands), this could cause:

  1. Increased memory consumption on the server
  2. Slow response times
  3. Large payload sizes affecting client performance

Verify that this is intentional and that there are practical limits on the number of chats per user, or consider re-introducing pagination or implementing a reasonable limit.

Check if there are database-level or application-level limits on chat count:


🏁 Script executed:

#!/bin/bash
# Description: Check for chat limits in persistence layer and schema

# Search for limits in persistence functions
rg -nP 'limit|LIMIT' lib/supabase/persistence.ts lib/actions/chat.ts -C 2

# Search for RLS policies or constraints in migrations
fd -e sql . -x cat {} \; | grep -i "limit\|check\|constraint" -C 2

Length of output: 2718


🏁 Script executed:

#!/bin/bash
# Description: Locate and inspect the getChats implementation to see if it includes any implicit limits or pagination
rg -nP 'getChats' -n -C10 lib/actions/chat.ts || rg -nP 'getChats' -n -C10 lib/supabase/persistence.ts

Length of output: 905


Missing user filter and pagination in getChats

  • In lib/actions/chat.ts getChats(): .from('chats').select('*') ignores the userId parameter, returning all chats for all users. Add .eq('user_id', userId) (and fix the shadowed userId binding).
  • No pagination or limit is applied—consider using .range() or .limit() to prevent excessive data loads.
🤖 Prompt for AI Agents
In app/api/chats/route.ts around lines 12-13, the handler calls getChats(userId)
but the underlying getChats implementation currently queries
`.from('chats').select('*')` which ignores the userId, may shadow the parameter,
and returns unbounded results; update getChats to use the provided userId
(remove any shadowing) and add a filter `.eq('user_id', userId)` plus a
pagination/limit call such as `.range(start, end)` or `.limit(n)` (and
validate/normalize start/end or limit inputs) to prevent returning all chats for
all users.

Comment on lines +12 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider reintroducing pagination for scalability.

Removing pagination means all chats for a user are loaded in a single request. For users with hundreds or thousands of chats, this could cause:

  • Slow API response times
  • High memory consumption on both server and client
  • Poor user experience with large datasets
  • Potential timeout errors

If the previous pagination was removed intentionally (e.g., because most users have few chats), consider adding it back with sensible defaults:

 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 limit = Math.min(parseInt(searchParams.get('limit') || '50'), 100);
+    const offset = Math.max(parseInt(searchParams.get('offset') || '0'), 0);
+
-    const chats = await getChats(userId);
+    const chats = await getChats(userId, { limit, offset });
     return NextResponse.json({ chats });
   } catch (error) {
     console.error('Error fetching chats:', error);
     return NextResponse.json({ error: 'Internal Server Error fetching chats' }, { status: 500 });
   }
 }

Alternatively, if pagination is truly unnecessary, add a comment explaining the rationale.

📝 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
const chats = await getChats(userId);
return NextResponse.json({ chats });
export async function GET(request: NextRequest) {
try {
const userId = await getCurrentUserIdOnServer();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Parse pagination parameters with sensible defaults and limits
const { searchParams } = new URL(request.url);
const limit = Math.min(parseInt(searchParams.get('limit') || '50', 10), 100);
const offset = Math.max(parseInt(searchParams.get('offset') || '0', 10), 0);
const chats = await getChats(userId, { limit, offset });
return NextResponse.json({ chats });
} catch (error) {
console.error('Error fetching chats:', error);
return NextResponse.json({ error: 'Internal Server Error fetching chats' }, { status: 500 });
}
}
🤖 Prompt for AI Agents
In app/api/chats/route.ts around lines 12-13, the handler currently returns all
chats for a user which is not scalable; change the endpoint to accept pagination
params (e.g., page and pageSize or cursor) with sensible defaults (page=1,
pageSize=20), update getChats to accept limit/offset or cursor and return
paginated results plus metadata (total or hasMore, page, pageSize), and return
NextResponse.json with { chats, page, pageSize, total/hasMore }; if you
intentionally want no pagination, add a concise comment explaining why (e.g.,
bounded small dataset) instead of leaving it implicit.

} catch (error) {
console.error('Error fetching chats:', error);
return NextResponse.json({ error: 'Internal Server Error fetching chats' }, { status: 500 });
Expand Down
47 changes: 11 additions & 36 deletions app/search/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,51 @@
import { notFound, redirect } from 'next/navigation';
import { Chat } from '@/components/chat';
import { getChat, getChatMessages } from '@/lib/actions/chat'; // Added getChatMessages
import { getChat, getChatMessages } from '@/lib/actions/chat';
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
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';
import type { AIMessage } from '@/lib/types';

export const maxDuration = 60;

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

export async function generateMetadata({ params }: SearchPageProps) {
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
const { id } = await params;
const chat = await getChat(id);
return {
Comment on lines 11 to 18
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Next.js params typing; remove unnecessary awaits.

params is not a Promise in Next.js route/page props.

-export interface SearchPageProps {
-  params: Promise<{ id: string }>;
-}
+export interface SearchPageProps {
+  params: { id: string };
+}
 
 export async function generateMetadata({ params }: SearchPageProps) {
-  const { id } = await params;
+  const { id } = params;
-export default async function SearchPage({ params }: SearchPageProps) {
-  const { id } = await params;
+export default async function SearchPage({ params }: SearchPageProps) {
+  const { id } = params;

Also applies to: 23-25

🤖 Prompt for AI Agents
In app/search/[id]/page.tsx around lines 11 to 18 (and similarly lines 23–25),
change the props typing to reflect Next.js passes params as a plain object (not
a Promise) by replacing "params: Promise<{ id: string }>" with "params: { id:
string }"; remove the unnecessary "await" when extracting id (use "const { id }
= params;" instead of "const { id } = await params;"); apply the same typing and
removal of awaits to the other function(s) on lines 23–25 that currently treat
params as a Promise.

title: chat?.title?.toString().slice(0, 50) || 'Search',
};
}

export default async function SearchPage({ params }: SearchPageProps) {
const { id } = await params; // Keep as is for now
const { id } = await params;
const userId = await getCurrentUserIdOnServer();

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('/');
}

const chat = await getChat(id, userId);
const chat = await getChat(id);

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.
};
});
const initialMessages = await getChatMessages(chat.id);

return (
<AI
initialAIState={{
chatId: chat.id,
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.
messages: initialMessages as AIMessage[],
}}
>
<MapDataProvider>
<Chat id={id} />
</MapDataProvider>
</AI>
);
}
}
10 changes: 4 additions & 6 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { useEffect, useState, useRef, ChangeEvent, forwardRef, useImperativeHandle } from 'react'
import type { AI, UIState } from '@/app/actions'
import { useUIState, useActions } from 'ai/rsc'
// Removed import of useGeospatialToolMcp as it's no longer used/available
import { cn } from '@/lib/utils'
import { UserMessage } from './user-message'
import { Button } from './ui/button'
Expand All @@ -15,16 +14,17 @@ interface ChatPanelProps {
messages: UIState
input: string
setInput: (value: string) => void
chatId: string
shareableLink: string
}
Comment on lines +17 to 19
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Remove or use newly added props.

chatId and shareableLink are not used. Either wire them where needed or drop to avoid dead props.

Also applies to: 25-25

🤖 Prompt for AI Agents
In components/chat-panel.tsx around lines 17-19 (and also line 25), the newly
added props chatId and shareableLink are declared but never used; either remove
them from the component props type/signature and from any call sites, or wire
them into the component where needed (e.g., pass chatId to data-fetching hooks
or event handlers and render or generate the shareableLink where required).
Update the component signature, any children or hooks that need these values,
and all callers to match the new prop shape to eliminate dead props and
TypeScript/prop warnings.


export interface ChatPanelRef {
handleAttachmentClick: () => void;
}

export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, input, setInput }, ref) => {
export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, input, setInput, chatId, shareableLink }, ref) => {
const [, setMessages] = useUIState<typeof AI>()
const { submit, clearChat } = useActions()
// Removed mcp instance as it's no longer passed to submit
const [isMobile, setIsMobile] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)
Expand All @@ -37,7 +37,6 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
}
}));

// Detect mobile layout
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 1024)
Expand Down Expand Up @@ -116,7 +115,6 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
inputRef.current?.focus()
}, [])

// New chat button (appears when there are messages)
if (messages.length > 0 && !isMobile) {
return (
<div
Expand Down Expand Up @@ -172,7 +170,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
<div
className={cn(
'relative flex items-start w-full',
isMobile && 'mobile-chat-input' // Apply mobile chat input styling
isMobile && 'mobile-chat-input'
)}
>
<input
Expand Down
Loading