-
-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Make example prompts interactive and conditionally visible #302
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
base: main
Are you sure you want to change the base?
Conversation
This commit implements the following changes: 1. The example prompts on the empty chat screen are now hidden by default and only appear when the user focuses on the chat input. 2. Clicking on an example prompt now submits it as a query to the AI, and the response is displayed in the chat. These changes were accomplished by: - Adding an `onFocus` handler to the `ChatPanel` component to detect when the user interacts with the input field. - Introducing an `isInputFocused` state in the `Chat` component to control the visibility of the example prompts in the `EmptyScreen` component. - Updating the `submitMessage` function in the `Chat` component to use the `submit` action from `ai/rsc`, which handles the entire chat flow.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds input focus handling: ChatPanel accepts an onFocus prop and forwards it to the Textarea; chat.tsx tracks isInputFocused state and passes it to EmptyScreen, which now conditionally renders example messages based on focus. Also augments chat message flow (nanoid ids, useActions().submit) and minor import/structural changes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant ChatPanel
participant Chat
participant EmptyScreen
participant Actions
Note over ChatPanel,Chat: Focus flow
User->>ChatPanel: focus input
ChatPanel-->>Chat: onFocus()
Chat->>Chat: set isInputFocused = true
Chat-->>EmptyScreen: isInputFocused=true
Note right of EmptyScreen: example messages render only when focused
Note over ChatPanel,Actions: Submit flow
User->>ChatPanel: submit(message)
ChatPanel-->>Chat: onSubmit(message)
Chat->>Chat: append UserMessage (id=nanoid)
Chat->>Actions: submit(message)
Actions-->>Chat: AI response
Chat->>Chat: append AI message
Chat-->>User: updated message list
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)components/chat.tsx (5)
🔇 Additional comments (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Example prompts remain visible after the input loses focus; no
onBluris implemented to resetisInputFocused(introduces UX inconsistency). onFocuswas added as a required prop toChatPanel, which risks breaking other call sites; make it optional and addonBlurfor symmetry.- The example-prompt submission logic is duplicated across mobile/desktop; extract a single handler and add basic error handling.
- Avoid
as anywhen appending the AI response; typeuseActions/useUIStateto matchAIand remove the cast.
Additional notes (1)
- Style |
components/chat.tsx:127-129
Minor cleanup: remove the stray{' '}and inline comment noise around the provider. These add noise without value.
Summary of changes
- ChatPanel: added an
onFocusprop and wired it to the textarea’sonFocusevent. - Chat: introduced
isInputFocusedstate, passedonFocusto ChatPanel, gated EmptyScreen example prompts withisInputFocused, and implemented example-prompt submission by appending aUserMessagethen awaitingsubmit(message)fromai/rsc. AddednanoidandUserMessageusage. Duplicated this logic for both mobile and desktop layouts. - EmptyScreen: now receives
isInputFocusedand only renders example prompts when the input is focused. Clicking a prompt calls the providedsubmitMessage(now used to kick off the chat flow).
| onFocus={onFocus} | ||
| onKeyDown={e => { |
There was a problem hiding this comment.
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
| submitMessage={async (message: string) => { | ||
| setMessages(currentMessages => [ | ||
| ...currentMessages, | ||
| { | ||
| id: nanoid(), | ||
| component: <UserMessage message={message} /> | ||
| } | ||
| ]) | ||
| const responseMessage = await submit(message) | ||
| setMessages(currentMessages => [ | ||
| ...currentMessages, | ||
| responseMessage as any | ||
| ]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example-prompt submission logic is duplicated in both mobile and desktop branches, making the component harder to maintain. There’s also no error handling if submit(message) throws, which could leave the UI in a confusing state. Extract this into a single handleSubmitExample function and reuse it, with basic try/catch for resilience.
Suggestion
Refactor to a single handler and reuse it in both places, adding basic error handling:
const handleSubmitExample = async (message: string) => {
setMessages(curr => [
...curr,
{ id: nanoid(), component: }
])
try {
const responseMessage = await submit(message)
setMessages(curr => [
...curr,
responseMessage
])
} catch (err) {
console.error('Failed to submit example message', err)
// Optionally append a simple error UI message here
}
}
Then pass:
in both mobile and desktop blocks.
Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this refactor
components/chat.tsx
Outdated
| setMessages(currentMessages => [ | ||
| ...currentMessages, | ||
| responseMessage as any | ||
| ]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid as any when appending the AI response—it undermines type-safety and invites runtime errors. You can usually type submit by providing the AI generic to useActions (and to useUIState) which removes the need for casting and ensures that the returned UI message conforms to your UIState item type.
Suggestion
Type your hooks to align with the AI actions and UI state, and remove the as any:
// near the top of the component
const [messages, setMessages] = useUIState()
const { submit } = useActions()
// later
const responseMessage = await submit(message)
setMessages(curr => [...curr, responseMessage])
Reply with "@CharlieHelps yes please" if you'd like me to add a commit that applies these type fixes
| onFocus={() => setIsInputFocused(true)} | ||
| /> |
There was a problem hiding this comment.
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
| {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> | ||
| ) | ||
| })} |
There was a problem hiding this comment.
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.
This commit implements the following changes: 1. The example prompts on the empty chat screen are now hidden by default and only appear when the user focuses on the chat input. 2. Clicking on an example prompt now submits it as a query to the AI, and the response is displayed in the chat. These changes were accomplished by: - Adding an `onFocus` handler to the `ChatPanel` component to detect when the user interacts with the input field. - Introducing an `isInputFocused` state in the `Chat` component to control the visibility of the example prompts in the `EmptyScreen` component. - Updating the `submitMessage` function in the `Chat` component to use the `submit` action from `ai/rsc`, which handles the entire chat flow. - Fixing a TypeScript error by explicitly typing the `currentMessages` parameter in the `setMessages` function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
components/chat-panel.tsx (1)
44-57: Clear input after submit to match chat UX and prevent double-send confusion.After queuing the user message and constructing FormData, clear the controlled input. Optional: disable submit while awaiting.
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault() setMessages(currentMessages => [ @@ const formData = new FormData(e.currentTarget) // Removed mcp argument from submit call const responseMessage = await submit(formData) setMessages(currentMessages => [...currentMessages, responseMessage as any]) + setInput('') }components/chat.tsx (2)
68-77: Context order bug: useMapData is called before MapDataProvider.Hooks consuming context must be inside the provider subtree. Currently, useMapData runs at the top of Chat, outside the provider used later in both mobile/desktop returns. This will read undefined/default context and can throw.
Introduce a child component that syncs the drawing context and place it inside MapDataProvider; remove the top-level useMapData call/effect.
- // Get mapData to access drawnFeatures - const { mapData } = useMapData(); - - // useEffect to call the server action when drawnFeatures changes - useEffect(() => { - if (id && mapData.drawnFeatures && mapData.drawnFeatures.length > 0) { - console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures); - updateDrawingContext(id, mapData.drawnFeatures); - } - }, [id, mapData.drawnFeatures]); + // Move drawing sync inside provider subtree + function DrawingContextSync({ chatId }: { chatId?: string }) { + const { mapData } = useMapData() + useEffect(() => { + if (chatId && mapData.drawnFeatures?.length) { + console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures) + updateDrawingContext(chatId, mapData.drawnFeatures) + } + }, [chatId, mapData.drawnFeatures]) + return null + }Then render inside each MapDataProvider (see next comments).
127-170: Apply the same onBlur wiring and move drawing sync inside the provider (desktop).
- Add onBlur to ChatPanel.
- Mount DrawingContextSync inside MapDataProvider.
- return ( - <MapDataProvider> - {' '} - {/* Add Provider */} + return ( + <MapDataProvider> + {/* Add Provider */} + <DrawingContextSync chatId={id} /> <div className="flex justify-start items-start"> @@ <ChatPanel messages={messages} input={input} setInput={setInput} - onFocus={() => setIsInputFocused(true)} + onFocus={() => setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} /> @@ <EmptyScreen isInputFocused={isInputFocused} submitMessage={async (message: string) => { setMessages(currentMessages => [ ...currentMessages, { id: nanoid(), component: <UserMessage message={message} /> } ]) - - const responseMessage = await submit(message) + const fd = new FormData() + fd.set('input', message) + const responseMessage = await submit(fd) setMessages(currentMessages => [ ...currentMessages, responseMessage as any ]) }} /> @@ </MapDataProvider> )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
components/chat-panel.tsx(2 hunks)components/chat.tsx(5 hunks)components/empty-screen.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/chat-panel.tsx (1)
components/ui/textarea.tsx (2)
TextareaProps(5-6)props(9-20)
components/chat.tsx (4)
components/chat-panel.tsx (1)
ChatPanel(21-184)components/empty-screen.tsx (1)
EmptyScreen(27-62)components/user-message.tsx (1)
UserMessage(10-22)components/map/map-data-context.tsx (1)
MapDataProvider(26-34)
| onFocus: () => void | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const [messages, setMessages] = useUIState() | ||
| const [aiState] = useAIState() | ||
| const { submit } = useActions() | ||
| const [isMobile, setIsMobile] = useState(false) |
There was a problem hiding this comment.
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.
| <ChatPanel | ||
| messages={messages} | ||
| input={input} | ||
| setInput={setInput} | ||
| onFocus={() => setIsInputFocused(true)} | ||
| /> |
There was a problem hiding this comment.
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.
| <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.
| <EmptyScreen | ||
| submitMessage={message => { | ||
| setInput(message) | ||
| isInputFocused={isInputFocused} | ||
| submitMessage={async (message: string) => { | ||
| setMessages(currentMessages => [ | ||
| ...currentMessages, | ||
| { | ||
| id: nanoid(), | ||
| component: <UserMessage message={message} /> | ||
| } | ||
| ]) | ||
| const responseMessage = await submit(message) | ||
| setMessages(currentMessages => [ | ||
| ...currentMessages, | ||
| responseMessage as any | ||
| ]) | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Unify submit() usage: pass FormData (keeps parity with ChatPanel and typical ai/rsc actions).
Current code calls submit(message) while ChatPanel uses submit(formData). Unless submit is overloaded, this may break. Prefer FormData to avoid type mismatch.
submitMessage={async (message: string) => {
setMessages(currentMessages => [
...currentMessages,
{
id: nanoid(),
component: <UserMessage message={message} />
}
])
- const responseMessage = await submit(message)
+ const fd = new FormData()
+ fd.set('input', message)
+ const responseMessage = await submit(fd)
setMessages(currentMessages => [
...currentMessages,
responseMessage as any
])
}}If submit(message) is intentionally supported, please confirm and ignore this change.
📝 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.
| <EmptyScreen | |
| submitMessage={message => { | |
| setInput(message) | |
| isInputFocused={isInputFocused} | |
| submitMessage={async (message: string) => { | |
| setMessages(currentMessages => [ | |
| ...currentMessages, | |
| { | |
| id: nanoid(), | |
| component: <UserMessage message={message} /> | |
| } | |
| ]) | |
| const responseMessage = await submit(message) | |
| setMessages(currentMessages => [ | |
| ...currentMessages, | |
| responseMessage as any | |
| ]) | |
| }} | |
| <EmptyScreen | |
| isInputFocused={isInputFocused} | |
| submitMessage={async (message: string) => { | |
| setMessages(currentMessages => [ | |
| ...currentMessages, | |
| { | |
| id: nanoid(), | |
| component: <UserMessage message={message} /> | |
| } | |
| ]) | |
| const fd = new FormData() | |
| fd.set('input', message) | |
| const responseMessage = await submit(fd) | |
| setMessages(currentMessages => [ | |
| ...currentMessages, | |
| responseMessage as any | |
| ]) | |
| }} |
🤖 Prompt for AI Agents
In components/chat.tsx around lines 99 to 114, the code calls submit(message)
while other callers (e.g., ChatPanel) pass a FormData; to unify behavior change
the submit invocation to build a FormData with the message (e.g., const form =
new FormData(); form.append('message', message)) and call submit(form) instead,
update any type annotations if submit expects FormData, and keep the existing
optimistic UI flow (append the user message before awaiting submit and then
append the response after await).
| {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> | ||
| ) | ||
| })} |
There was a problem hiding this comment.
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.
| {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.
User description
This commit implements the following changes:
These changes were accomplished by:
onFocushandler to theChatPanelcomponent to detect when the user interacts with the input field.isInputFocusedstate in theChatcomponent to control the visibility of the example prompts in theEmptyScreencomponent.submitMessagefunction in theChatcomponent to use thesubmitaction fromai/rsc, which handles the entire chat flow.PR Type
Enhancement
Description
Make example prompts conditionally visible on input focus
Enable interactive example prompt submission with AI responses
Update chat flow to handle direct message submission
Add focus state management for improved UX
Diagram Walkthrough
File Walkthrough
chat-panel.tsx
Add focus event handling to chat inputcomponents/chat-panel.tsx
onFocusprop to interface and componentonFocushandler to textarea inputchat.tsx
Implement interactive message submission flowcomponents/chat.tsx
isInputFocusedstate for controlling prompt visibilityuseActions,nanoid, andUserMessagefor message handlingsubmitMessageto directly submit and display AI responsesonFocushandler andisInputFocusedto child componentsempty-screen.tsx
Add conditional visibility for example promptscomponents/empty-screen.tsx
isInputFocusedprop to control example prompt visibilitySummary by CodeRabbit