mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
feat: implement ChatInput component for enhanced message input and handling in ChatPanel
This commit is contained in:
71
components/chat-input.tsx
Normal file
71
components/chat-input.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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}
|
||||
<CardFooter className="p-2">
|
||||
<ChatInput
|
||||
input={input}
|
||||
isLoading={isLoading}
|
||||
onSubmit={onFormSubmit}
|
||||
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>
|
||||
</Card>
|
||||
)
|
||||
|
||||
18
components/ui/textarea.tsx
Normal file
18
components/ui/textarea.tsx
Normal 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 }
|
||||
Reference in New Issue
Block a user