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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL="postgresql://user:password@host:port/db"
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

⚠️ Potential issue

Do not commit .env; move to .env.example and drop quotes.

Committing environment files is risky. Replace this with an example file and ensure .env is gitignored. Also fix the dotenv-linter warning by removing quotes.

Apply:

-DATABASE_URL="postgresql://user:password@host:port/db"
+DATABASE_URL=postgresql://<user>:<password>@<host>:<port>/<db>

Also:

  • Add to .gitignore:
+.env
+.env.local
  • Rename this file to .env.example (I can draft it if you want).
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🤖 Prompt for AI Agents
In .env around line 1, the committed environment file with quoted sensitive
values must be removed from the repo, replaced by an example file, and quotes
dropped; rename the current .env to .env.example (remove any real secrets and
unquote values), add entries .env and .env.local to .gitignore, and remove the
committed .env from git history/staging (e.g., git rm --cached .env and commit)
so only .env.example remains tracked.

12 changes: 12 additions & 0 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,17 @@ async function submit(formData?: FormData, skip?: boolean) {
};
}

async function clearChat() {
'use server'

const aiState = getMutableAIState<typeof AI>()

aiState.done({
chatId: nanoid(),
messages: []
})
}
Comment on lines +251 to +260
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Return new chatId and preserve other AI state on clear.

Return the generated chatId so clients can sync the URL, and keep other fields (e.g., isSharePage).

-async function clearChat() {
-  'use server'
-
-  const aiState = getMutableAIState<typeof AI>()
-
-  aiState.done({
-    chatId: nanoid(),
-    messages: []
-  })
-}
+async function clearChat() {
+  'use server'
+  const aiState = getMutableAIState<typeof AI>()
+  const prev = aiState.get()
+  const newChatId = nanoid()
+  aiState.done({
+    ...prev,
+    chatId: newChatId,
+    messages: []
+  })
+  return { chatId: newChatId }
+}
📝 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
async function clearChat() {
'use server'
const aiState = getMutableAIState<typeof AI>()
aiState.done({
chatId: nanoid(),
messages: []
})
}
async function clearChat() {
'use server'
const aiState = getMutableAIState<typeof AI>()
const prev = aiState.get()
const newChatId = nanoid()
aiState.done({
...prev,
chatId: newChatId,
messages: []
})
return { chatId: newChatId }
}
🤖 Prompt for AI Agents
In app/actions.tsx around lines 251 to 260, the clearChat function currently
replaces the AI state wholesale with a new object containing only chatId and
messages; change it to generate the new chatId, read the current mutable AI
state, and call aiState.done with a merged object that preserves existing fields
(for example isSharePage and any other properties) while setting chatId to the
new id and messages to an empty array, and finally return the generated chatId
so callers can sync the URL.


export type AIState = {
messages: AIMessage[];
chatId: string;
Expand All @@ -272,6 +283,7 @@ const initialUIState: UIState = [];
export const AI = createAI<AIState, UIState>({
actions: {
submit,
clearChat,
},
Comment on lines +286 to 287
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Expose typed actions to clients expecting clearChat result.

After returning { chatId } from clearChat, ensure clients await and use it to keep route and state consistent.

🤖 Prompt for AI Agents
In app/actions.tsx around lines 286-287, the actions export needs to expose
clearChat with an explicit return type so callers can await the returned {
chatId } and keep route/state consistent; update the clearChat function
signature to return Promise<{ chatId: string }>, ensure the exported actions
object includes the typed clearChat, and update any call sites to await
clearChat() and use the returned chatId to update route/state.

initialUIState,
initialAIState,
Expand Down
Binary file modified bun.lockb
Binary file not shown.
23 changes: 5 additions & 18 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'

import { useEffect, useState, useRef } from 'react'
import { useRouter } from 'next/navigation'
import type { AI, UIState } from '@/app/actions'
import { useUIState, useActions } from 'ai/rsc'
// Removed import of useGeospatialToolMcp as it's no longer used/available
Expand All @@ -20,13 +19,11 @@ interface ChatPanelProps {

export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
const [, setMessages] = useUIState<typeof AI>()
const { submit } = useActions()
const { submit, clearChat } = useActions()
// Removed mcp instance as it's no longer passed to submit
const [isButtonPressed, setIsButtonPressed] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const inputRef = useRef<HTMLTextAreaElement>(null)
const formRef = useRef<HTMLFormElement>(null)
const router = useRouter()

// Detect mobile layout
useEffect(() => {
Expand All @@ -38,19 +35,8 @@ export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
return () => window.removeEventListener('resize', checkMobile)
}, [])

useEffect(() => {
if (isButtonPressed) {
inputRef.current?.focus()
setIsButtonPressed(false)
}
}, [isButtonPressed])

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (isButtonPressed) {
handleClear()
setIsButtonPressed(false)
}
setMessages(currentMessages => [
...currentMessages,
{
Expand All @@ -64,16 +50,17 @@ export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
setMessages(currentMessages => [...currentMessages, responseMessage as any])
}

const handleClear = () => {
router.push('/')
const handleClear = async () => {
setMessages([])
await clearChat()
}
Comment on lines +53 to 56
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider URL-and-state alignment after “New” on desktop.

handleClear resets state but not the route; saved chats use /search/${chatId}. After clear, the path may still reference the old chat. Once clearChat returns the new chatId, replace the URL similar to MobileIconsBar.

Also applies to: 63-83

🤖 Prompt for AI Agents
In components/chat-panel.tsx around lines 53-56 (and similarly 63-83),
handleClear resets local state but doesn't update the URL so the route can still
reference the old chat; change it to await clearChat(), capture the returned new
chatId, then reset messages/state and call
router.replace(`/search/${newChatId}`) (use replace not push) to align URL and
state like MobileIconsBar; ensure router is available and handle
errors/undefined chatId gracefully.


useEffect(() => {
inputRef.current?.focus();
}, [])

// New chat button (appears when there are messages)
if (messages.length > 0 && !isButtonPressed && !isMobile) {
if (messages.length > 0 && !isMobile) {
return (
<div
className={cn(
Expand Down
11 changes: 7 additions & 4 deletions components/mobile-icons-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client'

import React from 'react'
import { useRouter } from 'next/navigation'
import { useUIState, useActions } from 'ai/rsc'
import { AI } from '@/app/actions'
import { Button } from '@/components/ui/button'
import {
Search,
Expand All @@ -18,10 +19,12 @@ import { MapToggle } from './map-toggle'
import { ModeToggle } from './mode-toggle'

export const MobileIconsBar: React.FC = () => {
const router = useRouter()
const [, setMessages] = useUIState<typeof AI>()
const { clearChat } = useActions()

const handleNewChat = () => {
router.push('/')
const handleNewChat = async () => {
setMessages([])
await clearChat()
}
Comment on lines +25 to 28
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Guard against double taps; optionally update route after clear.

Add a pending flag to prevent repeated clears, and (once clearChat returns the new chatId) replace the URL so it matches the fresh chat.

 export const MobileIconsBar: React.FC = () => {
-  const [, setMessages] = useUIState<typeof AI>()
-  const { clearChat } = useActions<typeof AI>()
+  const [, setMessages] = useUIState<typeof AI>()
+  const { clearChat } = useActions<typeof AI>()
+  const [isClearing, setIsClearing] = React.useState(false)
 
-  const handleNewChat = async () => {
-    setMessages([])
-    await clearChat()
+  const handleNewChat = async () => {
+    if (isClearing) return
+    setIsClearing(true)
+    try {
+      setMessages([])
+      const result = await clearChat() // expect { chatId }
+      if (result?.chatId && window.location.pathname.startsWith('/search/')) {
+        window.history.replaceState({}, '', `/search/${result.chatId}`)
+      }
+    } finally {
+      setIsClearing(false)
+    }
   }

And disable the button while clearing:

-      <Button variant="ghost" size="icon" onClick={handleNewChat}>
+      <Button variant="ghost" size="icon" onClick={handleNewChat} disabled={isClearing}>
         <Plus className="h-[1.2rem] w-[1.2rem]" />
       </Button>
📝 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 handleNewChat = async () => {
setMessages([])
await clearChat()
}
// components/mobile-icons-bar.tsx
export const MobileIconsBar: React.FC = () => {
const [, setMessages] = useUIState<typeof AI>()
const { clearChat } = useActions<typeof AI>()
const [isClearing, setIsClearing] = React.useState(false)
const handleNewChat = async () => {
if (isClearing) return
setIsClearing(true)
try {
setMessages([])
const result = await clearChat() // expect { chatId }
if (result?.chatId && window.location.pathname.startsWith('/search/')) {
window.history.replaceState({}, '', `/search/${result.chatId}`)
}
} finally {
setIsClearing(false)
}
}
return (
<div className="mobile-icons-bar">
{/* other icons… */}
<Button
variant="ghost"
size="icon"
onClick={handleNewChat}
disabled={isClearing}
>
<Plus className="h-[1.2rem] w-[1.2rem]" />
</Button>
{/* other icons… */}
</div>
)
}
🤖 Prompt for AI Agents
In components/mobile-icons-bar.tsx around lines 25–28, guard against double taps
by adding a local pending state (e.g., isClearing) and early-return if already
pending; set it true before starting, and false in a finally block. Call
setMessages([]) and then await clearChat(), capture its returned newChatId, and
if present call the router replace (or use history.replaceState) to update the
URL to the fresh chatId. Also bind the clear button's disabled attribute to the
pending flag so the UI is disabled while clearing.


return (
Expand Down