130 lines
4.9 KiB
TypeScript
130 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useRef, useEffect } from 'react'
|
|
import { Send, BookOpen, X } from 'lucide-react'
|
|
import { getNotebookIcon } from '@/lib/notebook-icon'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import { cn } from '@/lib/utils'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
|
|
interface ChatInputProps {
|
|
onSend: (message: string, notebookId?: string) => void
|
|
isLoading?: boolean
|
|
notebooks: any[]
|
|
currentNotebookId?: string | null
|
|
}
|
|
|
|
export function ChatInput({ onSend, isLoading, notebooks, currentNotebookId }: ChatInputProps) {
|
|
const { t } = useLanguage()
|
|
const [input, setInput] = useState('')
|
|
const [selectedNotebook, setSelectedNotebook] = useState<string | undefined>(currentNotebookId || undefined)
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (currentNotebookId) {
|
|
setSelectedNotebook(currentNotebookId)
|
|
}
|
|
}, [currentNotebookId])
|
|
|
|
const handleSend = () => {
|
|
if (!input.trim() || isLoading) return
|
|
onSend(input, selectedNotebook)
|
|
setInput('')
|
|
if (textareaRef.current) {
|
|
textareaRef.current.style.height = 'auto'
|
|
}
|
|
}
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault()
|
|
handleSend()
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (textareaRef.current) {
|
|
textareaRef.current.style.height = 'auto'
|
|
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`
|
|
}
|
|
}, [input])
|
|
|
|
return (
|
|
<div className="w-full relative">
|
|
<div className="relative flex flex-col bg-slate-50 dark:bg-[#202228] rounded-[24px] border border-slate-200/60 dark:border-white/10 shadow-sm focus-within:shadow-md focus-within:border-slate-300 dark:focus-within:border-white/20 transition-all duration-300 overflow-hidden">
|
|
|
|
{/* Input Area */}
|
|
<Textarea
|
|
ref={textareaRef}
|
|
placeholder={t('chat.placeholder')}
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
className="flex-1 min-h-[56px] max-h-[40vh] bg-transparent border-none focus-visible:ring-0 resize-none py-4 px-5 text-[15px] placeholder:text-slate-400"
|
|
/>
|
|
|
|
{/* Bottom Actions Bar */}
|
|
<div className="flex items-center justify-between px-3 pb-3 pt-1">
|
|
{/* Context Selector */}
|
|
<div className="flex items-center gap-2">
|
|
<Select
|
|
value={selectedNotebook || 'global'}
|
|
onValueChange={(val) => setSelectedNotebook(val === 'global' ? undefined : val)}
|
|
>
|
|
<SelectTrigger className="h-8 w-auto min-w-[130px] rounded-full bg-white dark:bg-[#1a1c22] border-slate-200 dark:border-white/10 shadow-sm text-xs font-medium gap-2 ring-offset-transparent focus:ring-0 focus:ring-offset-0 hover:bg-slate-50 dark:hover:bg-[#252830] transition-colors">
|
|
<BookOpen className="h-3.5 w-3.5 text-muted-foreground" />
|
|
<SelectValue placeholder={t('chat.allNotebooks')} />
|
|
</SelectTrigger>
|
|
<SelectContent className="rounded-xl shadow-lg border-slate-200 dark:border-white/10">
|
|
<SelectItem value="global" className="rounded-lg text-sm text-muted-foreground">{t('chat.inAllNotebooks')}</SelectItem>
|
|
{notebooks.map((nb) => (
|
|
<SelectItem key={nb.id} value={nb.id} className="rounded-lg text-sm">
|
|
{(() => {
|
|
const Icon = getNotebookIcon(nb.icon)
|
|
return <Icon className="w-3.5 h-3.5" />
|
|
})()} {nb.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
|
|
{selectedNotebook && (
|
|
<Badge variant="secondary" className="text-[10px] bg-primary/10 text-primary border-none rounded-full px-2.5 h-6 font-semibold tracking-wide">
|
|
{t('chat.active')}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{/* Send Button */}
|
|
<Button
|
|
disabled={!input.trim() || isLoading}
|
|
onClick={handleSend}
|
|
size="icon"
|
|
className={cn(
|
|
"rounded-full h-8 w-8 transition-all duration-200",
|
|
input.trim() ? "bg-primary text-primary-foreground shadow-sm hover:scale-105" : "bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500"
|
|
)}
|
|
>
|
|
<Send className="h-4 w-4 ml-0.5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-center mt-3">
|
|
<span className="text-[11px] text-muted-foreground/60 w-full block">
|
|
{t('chat.disclaimer')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|