chore: conditianal attachment and drag file to chat input
This commit is contained in:
parent
e3eb8e909b
commit
80dc491f9d
@ -34,6 +34,7 @@ import DropdownModelProvider from '@/containers/DropdownModelProvider'
|
|||||||
import { ModelLoader } from '@/containers/loaders/ModelLoader'
|
import { ModelLoader } from '@/containers/loaders/ModelLoader'
|
||||||
import DropdownToolsAvailable from '@/containers/DropdownToolsAvailable'
|
import DropdownToolsAvailable from '@/containers/DropdownToolsAvailable'
|
||||||
import { getConnectedServers } from '@/services/mcp'
|
import { getConnectedServers } from '@/services/mcp'
|
||||||
|
import { checkMmprojExists } from '@/services/models'
|
||||||
|
|
||||||
type ChatInputProps = {
|
type ChatInputProps = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -71,6 +72,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
>([])
|
>([])
|
||||||
const [connectedServers, setConnectedServers] = useState<string[]>([])
|
const [connectedServers, setConnectedServers] = useState<string[]>([])
|
||||||
const [isDragOver, setIsDragOver] = useState(false)
|
const [isDragOver, setIsDragOver] = useState(false)
|
||||||
|
const [hasMmproj, setHasMmproj] = useState(false)
|
||||||
|
|
||||||
// Check for connected MCP servers
|
// Check for connected MCP servers
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -92,6 +94,25 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
return () => clearInterval(intervalId)
|
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
|
// Check if there are active MCP servers
|
||||||
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0
|
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0
|
||||||
|
|
||||||
@ -283,8 +304,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
const handleDragEnter = (e: React.DragEvent) => {
|
const handleDragEnter = (e: React.DragEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
// Only allow drag if model supports mmproj
|
||||||
|
if (hasMmproj) {
|
||||||
setIsDragOver(true)
|
setIsDragOver(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDragLeave = (e: React.DragEvent) => {
|
const handleDragLeave = (e: React.DragEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -301,14 +325,21 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
// Ensure drag state is maintained during drag over
|
// Ensure drag state is maintained during drag over
|
||||||
|
if (hasMmproj) {
|
||||||
setIsDragOver(true)
|
setIsDragOver(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDrop = (e: React.DragEvent) => {
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setIsDragOver(false)
|
setIsDragOver(false)
|
||||||
|
|
||||||
|
// Only allow drop if model supports mmproj
|
||||||
|
if (!hasMmproj) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check if dataTransfer exists (it might not in some Tauri scenarios)
|
// Check if dataTransfer exists (it might not in some Tauri scenarios)
|
||||||
if (!e.dataTransfer) {
|
if (!e.dataTransfer) {
|
||||||
console.warn('No dataTransfer available in drop event')
|
console.warn('No dataTransfer available in drop event')
|
||||||
@ -332,6 +363,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
const clipboardItems = e.clipboardData?.items
|
const clipboardItems = e.clipboardData?.items
|
||||||
if (!clipboardItems) return
|
if (!clipboardItems) return
|
||||||
|
|
||||||
|
// Only allow paste if model supports mmproj
|
||||||
|
if (!hasMmproj) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const imageItems = Array.from(clipboardItems).filter((item) =>
|
const imageItems = Array.from(clipboardItems).filter((item) =>
|
||||||
item.type.startsWith('image/')
|
item.type.startsWith('image/')
|
||||||
)
|
)
|
||||||
@ -390,11 +426,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
isFocused && 'ring-1 ring-main-view-fg/10',
|
isFocused && 'ring-1 ring-main-view-fg/10',
|
||||||
isDragOver && 'ring-2 ring-accent border-accent'
|
isDragOver && 'ring-2 ring-accent border-accent'
|
||||||
)}
|
)}
|
||||||
data-drop-zone="true"
|
data-drop-zone={hasMmproj ? "true" : undefined}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={hasMmproj ? handleDragEnter : undefined}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={hasMmproj ? handleDragLeave : undefined}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={hasMmproj ? handleDragOver : undefined}
|
||||||
onDrop={handleDrop}
|
onDrop={hasMmproj ? handleDrop : undefined}
|
||||||
>
|
>
|
||||||
{uploadedFiles.length > 0 && (
|
{uploadedFiles.length > 0 && (
|
||||||
<div className="flex gap-3 items-center p-2 pb-0">
|
<div className="flex gap-3 items-center p-2 pb-0">
|
||||||
@ -455,7 +491,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
// When Shift+Enter is pressed, a new line is added (default behavior)
|
// When Shift+Enter is pressed, a new line is added (default behavior)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onPaste={handlePaste}
|
onPaste={hasMmproj ? handlePaste : undefined}
|
||||||
placeholder={t('common:placeholder.chatInput')}
|
placeholder={t('common:placeholder.chatInput')}
|
||||||
autoFocus
|
autoFocus
|
||||||
spellCheck={spellCheckChatInput}
|
spellCheck={spellCheckChatInput}
|
||||||
@ -489,7 +525,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* File attachment - show only for models with mmproj */}
|
{/* File attachment - show only for models with mmproj */}
|
||||||
{selectedModel?.settings?.offload_mmproj && (
|
{hasMmproj && (
|
||||||
<div
|
<div
|
||||||
className="h-6 p-1 ml-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"
|
className="h-6 p-1 ml-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1"
|
||||||
onClick={handleAttachmentClick}
|
onClick={handleAttachmentClick}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { localStorageKey } from '@/constants/localStorage'
|
|||||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||||
import { useFavoriteModel } from '@/hooks/useFavoriteModel'
|
import { useFavoriteModel } from '@/hooks/useFavoriteModel'
|
||||||
import { predefinedProviders } from '@/consts/providers'
|
import { predefinedProviders } from '@/consts/providers'
|
||||||
import { checkMmprojExists } from '@/services/models'
|
import { checkMmprojExistsAndUpdateOffloadMMprojSetting } from '@/services/models'
|
||||||
|
|
||||||
type DropdownModelProviderProps = {
|
type DropdownModelProviderProps = {
|
||||||
model?: ThreadModel
|
model?: ThreadModel
|
||||||
@ -102,7 +102,7 @@ const DropdownModelProvider = ({
|
|||||||
}
|
}
|
||||||
// Check mmproj existence for llamacpp models
|
// Check mmproj existence for llamacpp models
|
||||||
if (model?.provider === 'llamacpp') {
|
if (model?.provider === 'llamacpp') {
|
||||||
await checkMmprojExists(
|
await checkMmprojExistsAndUpdateOffloadMMprojSetting(
|
||||||
model.id as string,
|
model.id as string,
|
||||||
updateProvider,
|
updateProvider,
|
||||||
getProviderByName
|
getProviderByName
|
||||||
@ -114,7 +114,7 @@ const DropdownModelProvider = ({
|
|||||||
if (lastUsed && checkModelExists(lastUsed.provider, lastUsed.model)) {
|
if (lastUsed && checkModelExists(lastUsed.provider, lastUsed.model)) {
|
||||||
selectModelProvider(lastUsed.provider, lastUsed.model)
|
selectModelProvider(lastUsed.provider, lastUsed.model)
|
||||||
if (lastUsed.provider === 'llamacpp') {
|
if (lastUsed.provider === 'llamacpp') {
|
||||||
await checkMmprojExists(
|
await checkMmprojExistsAndUpdateOffloadMMprojSetting(
|
||||||
lastUsed.model,
|
lastUsed.model,
|
||||||
updateProvider,
|
updateProvider,
|
||||||
getProviderByName
|
getProviderByName
|
||||||
@ -282,7 +282,7 @@ const DropdownModelProvider = ({
|
|||||||
|
|
||||||
// Check mmproj existence for llamacpp models
|
// Check mmproj existence for llamacpp models
|
||||||
if (searchableModel.provider.provider === 'llamacpp') {
|
if (searchableModel.provider.provider === 'llamacpp') {
|
||||||
await checkMmprojExists(
|
await checkMmprojExistsAndUpdateOffloadMMprojSetting(
|
||||||
searchableModel.model.id,
|
searchableModel.model.id,
|
||||||
updateProvider,
|
updateProvider,
|
||||||
getProviderByName
|
getProviderByName
|
||||||
|
|||||||
@ -361,7 +361,7 @@ export const isToolSupported = async (modelId: string): Promise<boolean> => {
|
|||||||
* @param getProviderByName - Function to get provider by name
|
* @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
|
* @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,
|
modelId: string,
|
||||||
updateProvider?: (providerName: string, data: Partial<ModelProvider>) => void,
|
updateProvider?: (providerName: string, data: Partial<ModelProvider>) => void,
|
||||||
getProviderByName?: (providerName: string) => ModelProvider | undefined
|
getProviderByName?: (providerName: string) => ModelProvider | undefined
|
||||||
@ -465,3 +465,25 @@ export const checkMmprojExists = async (
|
|||||||
}
|
}
|
||||||
return { exists: false, settingsUpdated }
|
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<boolean> => {
|
||||||
|
try {
|
||||||
|
const engine = getEngine('llamacpp') as AIEngine & {
|
||||||
|
checkMmprojExists?: (id: string) => Promise<boolean>
|
||||||
|
}
|
||||||
|
if (engine && typeof engine.checkMmprojExists === 'function') {
|
||||||
|
return await engine.checkMmprojExists(modelId)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error checking mmproj for model ${modelId}:`, error)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user