diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index 4943b71fb..3b8126e0b 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -34,6 +34,7 @@ import DropdownModelProvider from '@/containers/DropdownModelProvider' import { ModelLoader } from '@/containers/loaders/ModelLoader' import DropdownToolsAvailable from '@/containers/DropdownToolsAvailable' import { getConnectedServers } from '@/services/mcp' +import { checkMmprojExists } from '@/services/models' type ChatInputProps = { className?: string @@ -71,6 +72,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { >([]) const [connectedServers, setConnectedServers] = useState([]) const [isDragOver, setIsDragOver] = useState(false) + const [hasMmproj, setHasMmproj] = useState(false) // Check for connected MCP servers useEffect(() => { @@ -92,6 +94,25 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { return () => clearInterval(intervalId) }, []) + // Check for mmproj existence when model changes + useEffect(() => { + const checkMmprojSupport = async () => { + if (selectedModel?.id) { + try { + const exists = await checkMmprojExists(selectedModel.id) + setHasMmproj(exists) + } catch (error) { + console.error('Error checking mmproj:', error) + setHasMmproj(false) + } + } else { + setHasMmproj(false) + } + } + + checkMmprojSupport() + }, [selectedModel?.id]) + // Check if there are active MCP servers const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0 @@ -283,7 +304,10 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { const handleDragEnter = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() - setIsDragOver(true) + // Only allow drag if model supports mmproj + if (hasMmproj) { + setIsDragOver(true) + } } const handleDragLeave = (e: React.DragEvent) => { @@ -301,7 +325,9 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { e.preventDefault() e.stopPropagation() // Ensure drag state is maintained during drag over - setIsDragOver(true) + if (hasMmproj) { + setIsDragOver(true) + } } const handleDrop = (e: React.DragEvent) => { @@ -309,6 +335,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { e.stopPropagation() setIsDragOver(false) + // Only allow drop if model supports mmproj + if (!hasMmproj) { + return + } + // Check if dataTransfer exists (it might not in some Tauri scenarios) if (!e.dataTransfer) { console.warn('No dataTransfer available in drop event') @@ -332,6 +363,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { const clipboardItems = e.clipboardData?.items if (!clipboardItems) return + // Only allow paste if model supports mmproj + if (!hasMmproj) { + return + } + const imageItems = Array.from(clipboardItems).filter((item) => item.type.startsWith('image/') ) @@ -390,11 +426,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { isFocused && 'ring-1 ring-main-view-fg/10', isDragOver && 'ring-2 ring-accent border-accent' )} - data-drop-zone="true" - onDragEnter={handleDragEnter} - onDragLeave={handleDragLeave} - onDragOver={handleDragOver} - onDrop={handleDrop} + data-drop-zone={hasMmproj ? "true" : undefined} + onDragEnter={hasMmproj ? handleDragEnter : undefined} + onDragLeave={hasMmproj ? handleDragLeave : undefined} + onDragOver={hasMmproj ? handleDragOver : undefined} + onDrop={hasMmproj ? handleDrop : undefined} > {uploadedFiles.length > 0 && (
@@ -455,7 +491,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { // When Shift+Enter is pressed, a new line is added (default behavior) } }} - onPaste={handlePaste} + onPaste={hasMmproj ? handlePaste : undefined} placeholder={t('common:placeholder.chatInput')} autoFocus spellCheck={spellCheckChatInput} @@ -489,7 +525,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { /> )} {/* File attachment - show only for models with mmproj */} - {selectedModel?.settings?.offload_mmproj && ( + {hasMmproj && (
=> { * @param getProviderByName - Function to get provider by name * @returns Promise<{exists: boolean, settingsUpdated: boolean}> - exists: true if mmproj.gguf exists, settingsUpdated: true if settings were modified */ -export const checkMmprojExists = async ( +export const checkMmprojExistsAndUpdateOffloadMMprojSetting = async ( modelId: string, updateProvider?: (providerName: string, data: Partial) => void, getProviderByName?: (providerName: string) => ModelProvider | undefined @@ -465,3 +465,25 @@ export const checkMmprojExists = async ( } return { exists: false, settingsUpdated } } + +/** + * Checks if mmproj.gguf file exists for a given model ID in the llamacpp provider. + * If mmproj.gguf exists, adds offload_mmproj setting with value true. + * @param modelId - The model ID to check for mmproj.gguf + * @param updateProvider - Function to update the provider state + * @param getProviderByName - Function to get provider by name + * @returns Promise<{exists: boolean, settingsUpdated: boolean}> - exists: true if mmproj.gguf exists, settingsUpdated: true if settings were modified + */ +export const checkMmprojExists = async (modelId: string): Promise => { + try { + const engine = getEngine('llamacpp') as AIEngine & { + checkMmprojExists?: (id: string) => Promise + } + if (engine && typeof engine.checkMmprojExists === 'function') { + return await engine.checkMmprojExists(modelId) + } + } catch (error) { + console.error(`Error checking mmproj for model ${modelId}:`, error) + } + return false +}