| import { useState, useMemo, useCallback, useRef } from 'react'; |
| import { useDrop } from 'react-dnd'; |
| import { useToastContext } from '@librechat/client'; |
| import { NativeTypes } from 'react-dnd-html5-backend'; |
| import { useQueryClient } from '@tanstack/react-query'; |
| import { useRecoilValue, useSetRecoilState } from 'recoil'; |
| import { |
| Tools, |
| QueryKeys, |
| Constants, |
| EToolResources, |
| EModelEndpoint, |
| mergeFileConfig, |
| AgentCapabilities, |
| isAssistantsEndpoint, |
| getEndpointFileConfig, |
| defaultAgentCapabilities, |
| } from 'librechat-data-provider'; |
| import type { DropTargetMonitor } from 'react-dnd'; |
| import type * as t from 'librechat-data-provider'; |
| import store, { ephemeralAgentByConvoId } from '~/store'; |
| import useFileHandling from './useFileHandling'; |
| import { isEphemeralAgent } from '~/common'; |
| import useLocalize from '../useLocalize'; |
|
|
| export default function useDragHelpers() { |
| const queryClient = useQueryClient(); |
| const { showToast } = useToastContext(); |
| const localize = useLocalize(); |
| const [showModal, setShowModal] = useState(false); |
| const [draggedFiles, setDraggedFiles] = useState<File[]>([]); |
| const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined; |
| const setEphemeralAgent = useSetRecoilState( |
| ephemeralAgentByConvoId(conversation?.conversationId ?? Constants.NEW_CONVO), |
| ); |
|
|
| const isAssistants = useMemo( |
| () => isAssistantsEndpoint(conversation?.endpoint), |
| [conversation?.endpoint], |
| ); |
|
|
| const { handleFiles } = useFileHandling(); |
|
|
| const handleOptionSelect = useCallback( |
| (toolResource: EToolResources | undefined) => { |
| |
| if (toolResource && toolResource !== EToolResources.file_search) { |
| setEphemeralAgent((prev) => ({ |
| ...prev, |
| [toolResource]: true, |
| })); |
| } |
| handleFiles(draggedFiles, toolResource); |
| setShowModal(false); |
| setDraggedFiles([]); |
| }, |
| [draggedFiles, handleFiles, setEphemeralAgent], |
| ); |
|
|
| |
| const handleFilesRef = useRef(handleFiles); |
| const conversationRef = useRef(conversation); |
|
|
| handleFilesRef.current = handleFiles; |
| conversationRef.current = conversation; |
|
|
| const handleDrop = useCallback( |
| (item: { files: File[] }) => { |
| |
| const currentEndpoint = conversationRef.current?.endpoint ?? 'default'; |
| const currentEndpointType = conversationRef.current?.endpointType ?? undefined; |
| const cfg = queryClient.getQueryData<t.FileConfig>([QueryKeys.fileConfig]); |
| if (cfg) { |
| const mergedCfg = mergeFileConfig(cfg); |
| const endpointCfg = getEndpointFileConfig({ |
| fileConfig: mergedCfg, |
| endpoint: currentEndpoint, |
| endpointType: currentEndpointType, |
| }); |
| if (endpointCfg?.disabled === true) { |
| showToast({ |
| message: localize('com_ui_attach_error_disabled'), |
| status: 'error', |
| }); |
| return; |
| } |
| } |
|
|
| if (isAssistants) { |
| handleFilesRef.current(item.files); |
| return; |
| } |
|
|
| const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]); |
| const agentsConfig = endpointsConfig?.[EModelEndpoint.agents]; |
| const capabilities = agentsConfig?.capabilities ?? defaultAgentCapabilities; |
| const fileSearchEnabled = capabilities.includes(AgentCapabilities.file_search) === true; |
| const codeEnabled = capabilities.includes(AgentCapabilities.execute_code) === true; |
| const contextEnabled = capabilities.includes(AgentCapabilities.context) === true; |
|
|
| |
| const agentId = conversationRef.current?.agent_id; |
| let fileSearchAllowedByAgent = true; |
| let codeAllowedByAgent = true; |
|
|
| if (agentId && !isEphemeralAgent(agentId)) { |
| |
| const agent = queryClient.getQueryData<t.Agent>([QueryKeys.agent, agentId]); |
| if (agent) { |
| const agentTools = agent.tools as string[] | undefined; |
| fileSearchAllowedByAgent = agentTools?.includes(Tools.file_search) ?? false; |
| codeAllowedByAgent = agentTools?.includes(Tools.execute_code) ?? false; |
| } else { |
| |
| fileSearchAllowedByAgent = false; |
| codeAllowedByAgent = false; |
| } |
| } |
|
|
| |
| const allImages = item.files.every((f) => f.type?.startsWith('image/')); |
|
|
| const shouldShowModal = |
| allImages || |
| (fileSearchEnabled && fileSearchAllowedByAgent) || |
| (codeEnabled && codeAllowedByAgent) || |
| contextEnabled; |
|
|
| if (!shouldShowModal) { |
| |
| handleFilesRef.current(item.files); |
| return; |
| } |
| setDraggedFiles(item.files); |
| setShowModal(true); |
| }, |
| [isAssistants, queryClient, showToast, localize], |
| ); |
|
|
| const [{ canDrop, isOver }, drop] = useDrop( |
| () => ({ |
| accept: [NativeTypes.FILE], |
| drop: handleDrop, |
| canDrop: () => true, |
| collect: (monitor: DropTargetMonitor) => { |
| |
| const isOver = monitor.isOver(); |
| const canDrop = monitor.canDrop(); |
| return { isOver, canDrop }; |
| }, |
| }), |
| [handleDrop], |
| ); |
|
|
| return { |
| canDrop, |
| isOver, |
| drop, |
| showModal, |
| setShowModal, |
| draggedFiles, |
| handleOptionSelect, |
| }; |
| } |
|
|