"use client"; import { useState, useCallback, useEffect, useRef, forwardRef, useImperativeHandle, } from "react"; import { ArrowRight, Check, FolderOpen, Library, Square, X, } from "lucide-react"; import { AddDocButton } from "./AddDocButton"; import { FileTypeIcon } from "../shared/FileTypeIcon"; import { AddDocumentsModal } from "../modals/AddDocumentsModal"; import { AssistantWorkflowModal } from "./AssistantWorkflowModal"; import { ApiKeyMissingPopup } from "../popups/ApiKeyMissingPopup"; import { ModelToggle } from "./ModelToggle"; import { useSelectedModel } from "@/app/hooks/useSelectedModel"; import { useUserProfile } from "@/contexts/UserProfileContext"; import { getModelProvider, isModelAvailable, type ModelProvider, } from "@/app/lib/modelAvailability"; import type { Document, Message } from "../shared/types"; import { cn } from "@/lib/utils"; export interface ChatInputHandle { addDoc: (doc: Document) => void; } interface Props { onSubmit: (message: Message) => void; onCancel: () => void; isLoading: boolean; hideAddDocButton?: boolean; hideWorkflowButton?: boolean; onProjectsClick?: () => void; projectName?: string; projectCmNumber?: string | null; } export const ChatInput = forwardRef(function ChatInput( { onSubmit, onCancel, isLoading, hideAddDocButton, hideWorkflowButton, onProjectsClick, projectName, projectCmNumber, }: Props, ref, ) { const [value, setValue] = useState(""); const [attachedDocs, setAttachedDocs] = useState([]); const [selectedWorkflow, setSelectedWorkflow] = useState<{ id: string; title: string; } | null>(null); const [model, setModel] = useSelectedModel(); const { profile } = useUserProfile(); const apiKeys = profile?.apiKeys; const textareaRef = useRef(null); const controlsRef = useRef(null); const [compactControls, setCompactControls] = useState(false); const [docSelectorOpen, setDocSelectorOpen] = useState(false); const [workflowModalOpen, setWorkflowModalOpen] = useState(false); const [apiKeyModalProvider, setApiKeyModalProvider] = useState(null); useImperativeHandle(ref, () => ({ addDoc: (doc: Document) => { setAttachedDocs((prev) => { if (prev.some((d) => d.id === doc.id)) return prev; return [...prev, doc]; }); }, })); useEffect(() => { const el = controlsRef.current; if (!el) return; const update = () => setCompactControls(el.offsetWidth < 430); update(); const observer = new ResizeObserver(update); observer.observe(el); return () => observer.disconnect(); }, []); const handleAddDocFromProject = useCallback((doc: Document) => { setAttachedDocs((prev) => { if (prev.some((d) => d.id === doc.id)) return prev; return [...prev, doc]; }); }, []); const handleAddDocsFromSelector = useCallback( (selectedDocs: Document[]) => { setAttachedDocs((prev) => { const existing = new Set(prev.map((d) => d.id)); return [ ...prev, ...selectedDocs.filter((d) => !existing.has(d.id)), ]; }); }, [], ); const handleChange = (e: React.ChangeEvent) => { setValue(e.target.value); const el = e.target; el.style.height = "auto"; el.style.height = `${el.scrollHeight}px`; }; const handleSubmit = () => { const query = value.trim(); if (!query || isLoading) return; if (apiKeys && !isModelAvailable(model, apiKeys)) { setApiKeyModalProvider(getModelProvider(model)); return; } setValue(""); if (textareaRef.current) { textareaRef.current.style.height = "auto"; } const files = attachedDocs.map((d) => ({ filename: d.filename, document_id: d.id, })); setAttachedDocs([]); const wf = selectedWorkflow; setSelectedWorkflow(null); onSubmit?.({ role: "user", content: query, files: files.length > 0 ? files : undefined, workflow: wf ?? undefined, model, }); }; const handleActionClick = () => { if (isLoading) { onCancel(); } else { handleSubmit(); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }; return ( <>
{/* Attached chips */} {(selectedWorkflow || attachedDocs.length > 0) && (
{selectedWorkflow && (
{selectedWorkflow.title}
)} {attachedDocs.map((doc) => { return (
{doc.filename}
); })}
)} {/* Input */}