feat: implement ChatInput component for enhanced message input and handling in ChatPanel

This commit is contained in:
dayuan.jiang
2025-03-22 13:15:51 +00:00
parent 1978be09f4
commit 1b40a3c456
3 changed files with 99 additions and 18 deletions

71
components/chat-input.tsx Normal file
View File

@@ -0,0 +1,71 @@
"use client"
import React, { useCallback, useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import { Loader2, Send } from "lucide-react"
interface ChatInputProps {
input: string
isLoading: boolean
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
}
export function ChatInput({ input, isLoading, onSubmit, onChange }: ChatInputProps) {
const textareaRef = useRef<HTMLTextAreaElement>(null)
// Auto-resize textarea based on content
const adjustTextareaHeight = useCallback(() => {
const textarea = textareaRef.current
if (textarea) {
textarea.style.height = "auto"
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`
}
}, [])
useEffect(() => {
adjustTextareaHeight()
}, [input, adjustTextareaHeight])
// Handle keyboard shortcuts
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
e.preventDefault()
const form = e.currentTarget.closest("form")
if (form && input.trim() && !isLoading) {
form.requestSubmit()
}
}
}
return (
<form onSubmit={onSubmit} className="w-full space-y-2">
<Textarea
ref={textareaRef}
value={input}
onChange={onChange}
onKeyDown={handleKeyDown}
placeholder="Describe what changes you want to make to the diagram... (Press Cmd/Ctrl + Enter to send)"
disabled={isLoading}
aria-label="Chat input"
className="min-h-[80px] resize-none transition-all duration-200"
/>
<div className="flex justify-end">
<Button
type="submit"
disabled={isLoading || !input.trim()}
className="transition-opacity"
aria-label={isLoading ? "Sending message..." : "Send message"}
>
{isLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Send className="mr-2 h-4 w-4" />
)}
Send
</Button>
</div>
</form>
)
}

View File

@@ -3,12 +3,10 @@
import type React from "react"
import { useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Loader2, Send } from "lucide-react"
import { useChat } from '@ai-sdk/react';
import { ChatInput } from "@/components/chat-input"
interface ChatPanelProps {
onDisplayChart: (xml: string) => void;
@@ -111,8 +109,8 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro
};
return (
<Card className="h-full flex flex-col rounded-none">
<CardHeader className="pb-2">
<Card className="h-full flex flex-col rounded-none py-0">
<CardHeader className="p-2 text-center">
<CardTitle>Chat with Diagram Generator</CardTitle>
</CardHeader>
<CardContent className="flex-grow overflow-hidden p-4">
@@ -165,19 +163,13 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro
<div ref={messagesEndRef} />
</ScrollArea>
</CardContent>
<CardFooter className="pt-2">
<form onSubmit={onFormSubmit} className="w-full flex space-x-2">
<Input
value={input}
onChange={handleInputChange}
placeholder="Describe what changes you want to make to the diagram..."
disabled={isLoading}
className="flex-grow"
/>
<Button type="submit" disabled={isLoading || !input.trim()}>
{isLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
</Button>
</form>
<CardFooter className="p-2">
<ChatInput
input={input}
isLoading={isLoading}
onSubmit={onFormSubmit}
onChange={handleInputChange}
/>
</CardFooter>
</Card>
)

View File

@@ -0,0 +1,18 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props}
/>
)
}
export { Textarea }