| | 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, |
| | }; |
| | } |
| |
|