import { AssistantIf, ComposerPrimitive, useAssistantState } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; import { AlertCircle, ArrowUpIcon, ChevronRightIcon, Loader2, Plug2, Plus, SquareIcon, } from "lucide-react"; import type { FC } from "react"; import { useCallback, useMemo, useRef, useState } from "react"; import { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon"; import { documentTypeCountsAtom } from "@/atoms/documents/document-query.atoms"; import { globalNewLLMConfigsAtom, llmPreferencesAtom, newLLMConfigsAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { ComposerAddAttachment } from "@/components/assistant-ui/attachment"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors"; import { cn } from "@/lib/utils"; const ConnectorIndicator: FC = () => { const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const { connectors, isLoading: connectorsLoading } = useSearchSourceConnectors( false, searchSpaceId ? Number(searchSpaceId) : undefined ); const { data: documentTypeCounts, isLoading: documentTypesLoading } = useAtomValue(documentTypeCountsAtom); const [isOpen, setIsOpen] = useState(false); const closeTimeoutRef = useRef(null); const isLoading = connectorsLoading || documentTypesLoading; const activeDocumentTypes = documentTypeCounts ? Object.entries(documentTypeCounts).filter(([_, count]) => count > 0) : []; // Count only active connectors (matching what's shown in the Active tab) const activeConnectorsCount = connectors.length; const hasConnectors = activeConnectorsCount > 0; const hasSources = hasConnectors || activeDocumentTypes.length > 0; const handleMouseEnter = useCallback(() => { // Clear any pending close timeout if (closeTimeoutRef.current) { clearTimeout(closeTimeoutRef.current); closeTimeoutRef.current = null; } setIsOpen(true); }, []); const handleMouseLeave = useCallback(() => { // Delay closing by 150ms for better UX closeTimeoutRef.current = setTimeout(() => { setIsOpen(false); }, 150); }, []); if (!searchSpaceId) return null; return ( {hasSources ? (
{activeConnectorsCount > 0 && (

Active Connectors

{activeConnectorsCount}
)} {activeConnectorsCount > 0 && (
{connectors.map((connector) => (
{getConnectorIcon(connector.connector_type, "size-3.5")} {connector.name}
))}
)} {activeDocumentTypes.length > 0 && ( <> {activeConnectorsCount > 0 && (

Documents

)}
{activeDocumentTypes.map(([docType, count]) => (
{getConnectorIcon(docType, "size-3.5")} {getDocumentTypeLabel(docType)} {count > 999 ? "999+" : count}
))}
)}
) : (

No sources yet

Add documents or connect data sources to enhance search results.

)}
); }; export const ComposerAction: FC = () => { // Check if any attachments are still being processed (running AND progress < 100) // When progress is 100, processing is done but waiting for send() const hasProcessingAttachments = useAssistantState(({ composer }) => composer.attachments?.some((att) => { const status = att.status; if (status?.type !== "running") return false; const progress = (status as { type: "running"; progress?: number }).progress; return progress === undefined || progress < 100; }) ); // Check if composer text is empty const isComposerEmpty = useAssistantState(({ composer }) => { const text = composer.text?.trim() || ""; return text.length === 0; }); // Check if a model is configured const { data: userConfigs } = useAtomValue(newLLMConfigsAtom); const { data: globalConfigs } = useAtomValue(globalNewLLMConfigsAtom); const { data: preferences } = useAtomValue(llmPreferencesAtom); const hasModelConfigured = useMemo(() => { if (!preferences) return false; const agentLlmId = preferences.agent_llm_id; if (agentLlmId === null || agentLlmId === undefined) return false; // Check if the configured model actually exists if (agentLlmId < 0) { return globalConfigs?.some((c) => c.id === agentLlmId) ?? false; } return userConfigs?.some((c) => c.id === agentLlmId) ?? false; }, [preferences, globalConfigs, userConfigs]); const isSendDisabled = hasProcessingAttachments || isComposerEmpty || !hasModelConfigured; return (
{/* Show processing indicator when attachments are being processed */} {hasProcessingAttachments && (
Processing...
)} {/* Show warning when no model is configured */} {!hasModelConfigured && !hasProcessingAttachments && (
Select a model
)} !thread.isRunning}> thread.isRunning}>
); };