"use client"; import { useRef, useEffect, KeyboardEvent, useState } from "react"; import { Plus, Mic, ArrowUp, Box, Search, Database, FileText, Code2, X, Loader2, Paperclip, RotateCcw, } from "lucide-react"; import { cn } from "@/lib/utils"; import { uploadAttachment, type AttachmentData } from "@/lib/api"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; interface Tool { id: string; icon: React.ElementType; label: string; } interface UploadingFile { id: string; file: File; status: "uploading" | "done" | "error"; attachment?: AttachmentData; error?: string; } interface GeminiInputProps { value: string; onChange: (val: string) => void; onSubmit: (attachments?: AttachmentData[]) => void; isLoading?: boolean; activeTools: Set; onActiveToolsChange: (tools: Set) => void; selectedModel: "flash" | "auto" | "pro"; onSelectedModelChange: (model: "flash" | "auto" | "pro") => void; } const TOOLS: Tool[] = [ { id: "search", icon: Search, label: "搜索" }, { id: "knowledge", icon: Database, label: "内部知识库" }, { id: "sandbox", icon: Box, label: "沙盒" }, { id: "document", icon: FileText, label: "文档生成" }, ]; export function GeminiInput({ value, onChange, onSubmit, isLoading, activeTools, onActiveToolsChange, selectedModel, onSelectedModelChange, }: GeminiInputProps) { const textareaRef = useRef(null); const fileInputRef = useRef(null); const [showTools, setShowTools] = useState(false); const [uploadingFiles, setUploadingFiles] = useState([]); // Auto-resize textarea useEffect(() => { const ta = textareaRef.current; if (!ta) return; ta.style.height = "auto"; ta.style.height = Math.min(ta.scrollHeight, 200) + "px"; }, [value]); const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (canSend) handleSubmit(); } }; const toggleTool = (id: string) => { const next = new Set(activeTools); if (next.has(id)) { next.delete(id); } else { next.add(id); } onActiveToolsChange(next); }; const hasUploading = uploadingFiles.some((f) => f.status === "uploading"); const canSend = value.trim().length > 0 && !isLoading && !hasUploading; const handleSubmit = () => { const attachments = uploadingFiles .filter((f) => f.status === "done" && f.attachment) .map((f) => f.attachment!); onSubmit(attachments.length > 0 ? attachments : undefined); setUploadingFiles([]); }; const doUpload = async (entry: UploadingFile) => { try { const attachment = await uploadAttachment(entry.file); setUploadingFiles((prev) => prev.map((f) => f.id === entry.id ? { ...f, status: "done", attachment } : f ) ); } catch (err) { setUploadingFiles((prev) => prev.map((f) => f.id === entry.id ? { ...f, status: "error", error: err instanceof Error ? err.message : "Upload failed" } : f ) ); } }; const handleFileUpload = (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; const newEntries: UploadingFile[] = Array.from(files).map((file) => ({ id: `upload-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, file, status: "uploading" as const, })); setUploadingFiles((prev) => [...prev, ...newEntries]); // Start uploads newEntries.forEach((entry) => doUpload(entry)); // Reset file input so the same file can be selected again e.target.value = ""; }; const handleRetryUpload = (entry: UploadingFile) => { setUploadingFiles((prev) => prev.map((f) => f.id === entry.id ? { ...f, status: "uploading", error: undefined } : f ) ); doUpload({ ...entry, status: "uploading" }); }; const handleRemoveFile = (id: string) => { setUploadingFiles((prev) => prev.filter((f) => f.id !== id)); }; const formatSize = (bytes: number): string => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; return (
{/* Input container */}
{/* Uploaded files preview */} {uploadingFiles.length > 0 && (
{uploadingFiles.map((entry) => (
{entry.status === "uploading" && ( )} {entry.status === "done" && ( )} {entry.status === "error" && ( )} {entry.file.name} {entry.status === "done" && entry.attachment && ( {formatSize(entry.attachment.size_bytes)} )} {entry.status === "error" && ( )}
))}
)} {/* Main input row */}
{/* Left: File upload button */} {/* Tools toggle */} {/* Textarea */}