+ {/* Sidebar */} + ({ id, title }))} + activeConversationId={activeConvId} + onNewChat={handleNewChat} + onSelectConversation={handleSelectConversation} + onOpenExtensions={handleOpenExtensions} + connectedExtensionsCount={extensions.filter((e) => e.connected).length} + /> + + {/* Main content area */} +
+ {/* Top bar */} + setSidebarOpen((v) => !v)} + /> + + {/* Messages or welcome */} +
+ {messages.length === 0 && !isLoading ? ( + <> + + {/* 显示工单摘要(仅当工单系统已连接) */} + {ticketSystemConnected && } + + ) : ( +
+ {/* 对话中也显示工单摘要 */} + {ticketSystemConnected && } + {messages.map((msg) => ( + + ))} + {isLoading && } +
+
+ )} +
+ + {/* Input */} + +
+ + {/* Extensions Panel */} + setExtensionsPanelOpen(false)} + extensions={extensions} + onUpdateExtension={handleUpdateExtension} + /> +
+ ); +} diff --git a/components/gemini/GeminiInput.tsx b/components/gemini/GeminiInput.tsx new file mode 100644 index 0000000..10ac890 --- /dev/null +++ b/components/gemini/GeminiInput.tsx @@ -0,0 +1,272 @@ +"use client"; + +import { useRef, useEffect, KeyboardEvent, useState } from "react"; +import { + Plus, + Mic, + ArrowUp, + Box, + Search, + Database, + FileText, + Code2, + X, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface Tool { + id: string; + icon: React.ElementType; + label: string; +} + +interface GeminiInputProps { + value: string; + onChange: (val: string) => void; + onSubmit: () => void; + isLoading?: boolean; +} + +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, +}: GeminiInputProps) { + const textareaRef = useRef(null); + const fileInputRef = useRef(null); + const [selectedModel, setSelectedModel] = useState<"flash" | "pro">("pro"); + const [activeTools, setActiveTools] = useState>(new Set()); + const [showTools, setShowTools] = useState(false); + + // 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 (value.trim() && !isLoading) onSubmit(); + } + }; + + const toggleTool = (id: string) => { + setActiveTools((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + const removeTool = (id: string) => { + setActiveTools((prev) => { + const next = new Set(prev); + next.delete(id); + return next; + }); + }; + + const canSend = value.trim().length > 0 && !isLoading; + + const handleFileUpload = (e: React.ChangeEvent) => { + const files = e.target.files; + if (files && files.length > 0) { + // Handle file upload + console.log("上传文件:", files[0].name); + } + }; + + return ( +
+
+ {/* Input container */} +
+ {/* Selected tools chips (auto show when has active tools) */} + {activeTools.size > 0 && ( +
+ {Array.from(activeTools).map((toolId) => { + const tool = TOOLS.find((t) => t.id === toolId); + if (!tool) return null; + const Icon = tool.icon; + return ( + + + {tool.label} + + + ); + })} +
+ )} + + {/* Main input row */} +
+ {/* Left: File upload button */} + + + + {/* Tools toggle */} + + + {/* Textarea */} +