mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-03 14:52:28 +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 type React from "react"
|
||||||
import { useRef, useEffect } 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 { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Loader2, Send } from "lucide-react"
|
|
||||||
import { useChat } from '@ai-sdk/react';
|
import { useChat } from '@ai-sdk/react';
|
||||||
|
import { ChatInput } from "@/components/chat-input"
|
||||||
|
|
||||||
interface ChatPanelProps {
|
interface ChatPanelProps {
|
||||||
onDisplayChart: (xml: string) => void;
|
onDisplayChart: (xml: string) => void;
|
||||||
@@ -111,8 +109,8 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="h-full flex flex-col rounded-none">
|
<Card className="h-full flex flex-col rounded-none py-0">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="p-2 text-center">
|
||||||
<CardTitle>Chat with Diagram Generator</CardTitle>
|
<CardTitle>Chat with Diagram Generator</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex-grow overflow-hidden p-4">
|
<CardContent className="flex-grow overflow-hidden p-4">
|
||||||
@@ -165,19 +163,13 @@ export default function ChatPanel({ onDisplayChart, onFetchChart }: ChatPanelPro
|
|||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="pt-2">
|
<CardFooter className="p-2">
|
||||||
<form onSubmit={onFormSubmit} className="w-full flex space-x-2">
|
<ChatInput
|
||||||
<Input
|
input={input}
|
||||||
value={input}
|
isLoading={isLoading}
|
||||||
onChange={handleInputChange}
|
onSubmit={onFormSubmit}
|
||||||
placeholder="Describe what changes you want to make to the diagram..."
|
onChange={handleInputChange}
|
||||||
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>
|
</CardFooter>
|
||||||
</Card>
|
</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