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
9 changes: 8 additions & 1 deletion components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ interface ChatPanelProps {
messages: UIState
input: string
setInput: (value: string) => void
onFocus: () => void
}
Comment on lines +18 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.

⚠️ Potential issue | 🟠 Major

Missing onBlur wiring means prompts never hide after focus leaves.

You added onFocus but not onBlur, so isInputFocused becomes sticky. Expose onBlur in props and forward it to Textarea so EmptyScreen hides when focus is lost.

 interface ChatPanelProps {
   messages: UIState
   input: string
   setInput: (value: string) => void
-  onFocus: () => void
+  onFocus: () => void
+  onBlur?: () => void
 }

 export function ChatPanel({
   messages,
   input,
   setInput,
-  onFocus
+  onFocus,
+  onBlur
 }: ChatPanelProps) {
@@
           <Textarea
             ref={inputRef}
             name="input"
@@
             onChange={e => {
               setInput(e.target.value)
             }}
             onFocus={onFocus}
+            onBlur={onBlur}

Also applies to: 21-26, 112-131

🤖 Prompt for AI Agents
In components/chat-panel.tsx around lines 18-19 (also apply fixes at 21-26 and
112-131): the props type exposes only onFocus so the component never
receives/forwards onBlur, causing isInputFocused to remain true and the
EmptyScreen to stay visible; add onBlur?: () => void to the component props,
accept onBlur in the component parameter list, and pass it through to the
Textarea (or input) element alongside onFocus so focus loss toggles state
correctly and EmptyScreen hides when focus leaves.


export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
export function ChatPanel({
messages,
input,
setInput,
onFocus
}: ChatPanelProps) {
const [, setMessages] = useUIState<typeof AI>()
const { submit, clearChat } = useActions()
// Removed mcp instance as it's no longer passed to submit
Expand Down Expand Up @@ -121,6 +127,7 @@ export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
onChange={e => {
setInput(e.target.value)
}}
onFocus={onFocus}
onKeyDown={e => {
Comment on lines +130 to 131

Choose a reason for hiding this comment

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

onFocus is introduced as a required prop, which creates a silent breaking change for any other usages of ChatPanel that don’t pass it. Also, you only ever set focus state to true and never reset it on blur, so the example prompts will remain visible after the input loses focus. Make the prop optional to preserve backwards-compatibility and also add an onBlur to correctly hide example prompts when the input is no longer focused.

Suggestion

Make the focus handlers optional and add an onBlur prop, then pass it through to the textarea and wire it up in callers.

Example changes:

  • In components/chat-panel.tsx

    interface ChatPanelProps {
    messages: UIState
    input: string
    setInput: (value: string) => void
    onFocus?: () => void
    onBlur?: () => void
    }

    export function ChatPanel({ messages, input, setInput, onFocus, onBlur }: ChatPanelProps) {
    // ...

    <Textarea // ... onFocus={onFocus} onBlur={onBlur} // ... /> }
  • In components/chat.tsx (both usages):

    <ChatPanel
    messages={messages}
    input={input}
    setInput={setInput}
    onFocus={() => setIsInputFocused(true)}
    onBlur={() => setIsInputFocused(false)}
    />

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

if (
e.key === 'Enter' &&
Expand Down
70 changes: 56 additions & 14 deletions components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import { ChatPanel } from './chat-panel'
import { ChatMessages } from './chat-messages'
import { EmptyScreen } from './empty-screen'
import { Mapbox } from './map/mapbox-map'
import { useUIState, useAIState } from 'ai/rsc'
import { useUIState, useAIState, useActions } from 'ai/rsc'
import { nanoid } from 'nanoid'
import { UserMessage } from './user-message'
import { UIState } from '@/app/actions'
import MobileIconsBar from './mobile-icons-bar'
import { useProfileToggle, ProfileToggleEnum } from "@/components/profile-toggle-context";
import { useProfileToggle, ProfileToggleEnum } from '@/components/profile-toggle-context'
import SettingsView from "@/components/settings/settings-view";
import { MapDataProvider, useMapData } from './map/map-data-context'; // Add this and useMapData
import { updateDrawingContext } from '@/lib/actions/chat'; // Import the server action
Expand All @@ -20,12 +23,14 @@ type ChatProps = {
export function Chat({ id }: ChatProps) {
const router = useRouter()
const path = usePathname()
const [messages] = useUIState()
const [messages, setMessages] = useUIState()
const [aiState] = useAIState()
const { submit } = useActions()
const [isMobile, setIsMobile] = useState(false)
Comment on lines +26 to 29
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

Type UI state for consistency.

Optional: mirror ChatPanel’s usage and specify the AI UI state type parameter for useUIState to keep types aligned.

-  const [messages, setMessages] = useUIState()
+  const [messages, setMessages] = useUIState<typeof import('@/app/actions').AI>()
🤖 Prompt for AI Agents
In components/chat.tsx around lines 25 to 28, the UI state hooks are used
without explicit types which can cause inconsistent typings compared to
ChatPanel; update the hook calls to use the same generic type parameters as
ChatPanel (e.g., provide the appropriate Chat UI state type to useUIState and
the matching AI UI state type to useAIState), import those types if needed, and
update any dependent variables (messages, setMessages, aiState) to reflect the
typed generics so the component’s state shapes match ChatPanel’s usage.

const { activeView } = useProfileToggle();
const { activeView } = useProfileToggle()
const [input, setInput] = useState('')
const [showEmptyScreen, setShowEmptyScreen] = useState(false)
const [isInputFocused, setIsInputFocused] = useState(false)

useEffect(() => {
setShowEmptyScreen(messages.length === 0)
Expand Down Expand Up @@ -83,13 +88,30 @@ export function Chat({ id }: ChatProps) {
<MobileIconsBar />
</div>
<div className="mobile-chat-input-area">
<ChatPanel messages={messages} input={input} setInput={setInput} />
<ChatPanel
messages={messages}
input={input}
setInput={setInput}
onFocus={() => setIsInputFocused(true)}
/>
Comment on lines +95 to +96

Choose a reason for hiding this comment

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

You set onFocus={() => setIsInputFocused(true)} but never reset it on blur, so once a user focuses the input, example prompts remain visible even after the input loses focus. Add an onBlur to restore isInputFocused to false.

Suggestion

Pass an onBlur handler to ChatPanel to keep isInputFocused accurate:

<ChatPanel
messages={messages}
input={input}
setInput={setInput}
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
/>

Implement onBlur in ChatPanel as shown in the earlier suggestion.

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

Comment on lines +91 to +96
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 | 🟠 Major

Pair onFocus with onBlur to collapse prompts when input loses focus (mobile).

Wire onBlur to reset state.

             <ChatPanel
               messages={messages}
               input={input}
               setInput={setInput}
-              onFocus={() => setIsInputFocused(true)}
+              onFocus={() => setIsInputFocused(true)}
+              onBlur={() => setIsInputFocused(false)}
             />
📝 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
<ChatPanel
messages={messages}
input={input}
setInput={setInput}
onFocus={() => setIsInputFocused(true)}
/>
<ChatPanel
messages={messages}
input={input}
setInput={setInput}
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
/>
🤖 Prompt for AI Agents
In components/chat.tsx around lines 90 to 95, the ChatPanel is wired with an
onFocus handler but lacks a paired onBlur to reset focus state; add an onBlur
prop that calls setIsInputFocused(false) (or a small handler that does this) so
that when the input loses focus on mobile the prompt collapse logic runs; ensure
the ChatPanel accepts and forwards the onBlur prop to the actual input element
and that setIsInputFocused is in scope.

</div>
<div className="mobile-chat-messages-area">
{showEmptyScreen ? (
<EmptyScreen
submitMessage={message => {
setInput(message)
isInputFocused={isInputFocused}
submitMessage={async (message: string) => {
setMessages((currentMessages: UIState) => [
...currentMessages,
{
id: nanoid(),
component: <UserMessage message={message} />
}
])
const responseMessage = await submit(message)
setMessages((currentMessages: UIState) => [
...currentMessages,
responseMessage as any
])
}}
/>
) : (
Expand All @@ -98,20 +120,40 @@ export function Chat({ id }: ChatProps) {
</div>
</div>
</MapDataProvider>
);
)
}

// Desktop layout
return (
<MapDataProvider> {/* Add Provider */}
<MapDataProvider>
{' '}
{/* Add Provider */}
<div className="flex justify-start items-start">
{/* This is the new div for scrolling */}
<div className="w-1/2 flex flex-col space-y-3 md:space-y-4 px-8 sm:px-12 pt-12 md:pt-14 pb-4 h-[calc(100vh-0.5in)] overflow-y-auto">
<ChatPanel messages={messages} input={input} setInput={setInput} />
<ChatPanel
messages={messages}
input={input}
setInput={setInput}
onFocus={() => setIsInputFocused(true)}
/>
{showEmptyScreen ? (
<EmptyScreen
submitMessage={message => {
setInput(message)
isInputFocused={isInputFocused}
submitMessage={async (message: string) => {
setMessages((currentMessages: UIState) => [
...currentMessages,
{
id: nanoid(),
component: <UserMessage message={message} />
}
])

const responseMessage = await submit(message)
setMessages((currentMessages: UIState) => [
...currentMessages,
responseMessage as any
])
}}
/>
) : (
Expand All @@ -126,5 +168,5 @@ export function Chat({ id }: ChatProps) {
</div>
</div>
</MapDataProvider>
);
)
}
43 changes: 23 additions & 20 deletions components/empty-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,36 @@ const exampleMessages = [
export function EmptyScreen({
submitMessage,
className,
isInputFocused
}: {
submitMessage: (message: string) => void;
className?: string;
submitMessage: (message: string) => void
className?: string
isInputFocused: boolean
}) {
return (
<div className={`mx-auto w-full transition-all ${className}`}>
<div className="bg-background p-2">
<div className="mt-4 flex flex-col items-start space-y-2 mb-4">
{exampleMessages.map((item) => {
const Icon = item.icon;
return (
<Button
key={item.message} // Use a unique property as the key.
variant="link"
className="h-auto p-0 text-base flex items-center"
name={item.message}
onClick={async () => {
submitMessage(item.message);
}}
>
<Icon size={16} className="mr-2 text-muted-foreground" />
{item.heading}
</Button>
);
})}
{isInputFocused &&
exampleMessages.map(item => {
const Icon = item.icon
return (
<Button
key={item.message} // Use a unique property as the key.
variant="link"
className="h-auto p-0 text-base flex items-center"
name={item.message}
onClick={async () => {
submitMessage(item.message)
}}
>
<Icon size={16} className="mr-2 text-muted-foreground" />
{item.heading}
</Button>
)
})}
Comment on lines +40 to +57

Choose a reason for hiding this comment

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

The visibility of example prompts is tied to isInputFocused, which is set to true on input focus but never reset to false on blur. This means the prompts can remain visible when the input is not focused. Once you add the onBlur logic in ChatPanel and wire it in Chat, this will behave as intended.

Comment on lines +40 to +57
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

Prevent accidental form submission and drop unnecessary async.

  • Buttons default to submit in form contexts; be explicit with type="button".
  • The onClick handler is marked async but doesn’t await; remove async for clarity.
-                <Button
+                <Button
                   key={item.message} // Use a unique property as the key.
                   variant="link"
                   className="h-auto p-0 text-base flex items-center"
                   name={item.message}
-                  onClick={async () => {
+                  type="button"
+                  onClick={() => {
                     submitMessage(item.message)
                   }}
                 >
📝 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
{isInputFocused &&
exampleMessages.map(item => {
const Icon = item.icon
return (
<Button
key={item.message} // Use a unique property as the key.
variant="link"
className="h-auto p-0 text-base flex items-center"
name={item.message}
onClick={async () => {
submitMessage(item.message)
}}
>
<Icon size={16} className="mr-2 text-muted-foreground" />
{item.heading}
</Button>
)
})}
{isInputFocused &&
exampleMessages.map(item => {
const Icon = item.icon
return (
<Button
key={item.message} // Use a unique property as the key.
variant="link"
className="h-auto p-0 text-base flex items-center"
name={item.message}
type="button"
onClick={() => {
submitMessage(item.message)
}}
>
<Icon size={16} className="mr-2 text-muted-foreground" />
{item.heading}
</Button>
)
})}
🤖 Prompt for AI Agents
In components/empty-screen.tsx around lines 40 to 57, the mapped Button elements
risk submitting the parent form because their type is not specified and the
onClick handlers are unnecessarily marked async; change each Button to include
type="button" to prevent accidental form submission, and remove the async
keyword from the onClick handler (call submitMessage(item.message) directly) to
avoid an unused async function and clarify intent.

</div>
</div>
</div>
);
)
}