Merge pull request #6493 from menloresearch/fix/edit-dialog
chore: prevent click outside for edit dialog
This commit is contained in:
commit
b4fd7c300a
@ -236,7 +236,11 @@ export default function AddEditAssistant({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent
|
||||||
|
onInteractOutside={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{editingKey
|
{editingKey
|
||||||
|
|||||||
@ -303,11 +303,16 @@ export default function AddEditMCPServer({
|
|||||||
const serverConfig = config as MCPServerConfig
|
const serverConfig = config as MCPServerConfig
|
||||||
|
|
||||||
// Validate type field if present
|
// Validate type field if present
|
||||||
if (serverConfig.type && !['stdio', 'http', 'sse'].includes(serverConfig.type)) {
|
if (
|
||||||
setError(t('mcp-servers:editJson.errorInvalidType', {
|
serverConfig.type &&
|
||||||
serverName: trimmedServerName,
|
!['stdio', 'http', 'sse'].includes(serverConfig.type)
|
||||||
type: serverConfig.type
|
) {
|
||||||
}))
|
setError(
|
||||||
|
t('mcp-servers:editJson.errorInvalidType', {
|
||||||
|
serverName: trimmedServerName,
|
||||||
|
type: serverConfig.type,
|
||||||
|
})
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +371,12 @@ export default function AddEditMCPServer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent showCloseButton={false}>
|
<DialogContent
|
||||||
|
showCloseButton={false}
|
||||||
|
onInteractOutside={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center justify-between">
|
<DialogTitle className="flex items-center justify-between">
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@ -61,7 +61,11 @@ export default function EditJsonMCPserver({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent
|
||||||
|
onInteractOutside={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{serverName
|
{serverName
|
||||||
|
|||||||
@ -44,129 +44,142 @@ export const ImportVisionModelDialog = ({
|
|||||||
>(null)
|
>(null)
|
||||||
const [isValidatingMmproj, setIsValidatingMmproj] = useState(false)
|
const [isValidatingMmproj, setIsValidatingMmproj] = useState(false)
|
||||||
|
|
||||||
const validateGgufFile = useCallback(async (
|
const validateGgufFile = useCallback(
|
||||||
filePath: string,
|
async (filePath: string, fileType: 'model' | 'mmproj'): Promise<void> => {
|
||||||
fileType: 'model' | 'mmproj'
|
|
||||||
): Promise<void> => {
|
|
||||||
if (fileType === 'model') {
|
|
||||||
setIsValidating(true)
|
|
||||||
setValidationError(null)
|
|
||||||
} else {
|
|
||||||
setIsValidatingMmproj(true)
|
|
||||||
setMmprojValidationError(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Handle validation differently for model files vs mmproj files
|
|
||||||
if (fileType === 'model') {
|
if (fileType === 'model') {
|
||||||
// For model files, use the standard validateGgufFile method
|
setIsValidating(true)
|
||||||
if (typeof serviceHub.models().validateGgufFile === 'function') {
|
setValidationError(null)
|
||||||
const result = await serviceHub.models().validateGgufFile(filePath)
|
} else {
|
||||||
|
setIsValidatingMmproj(true)
|
||||||
|
setMmprojValidationError(null)
|
||||||
|
}
|
||||||
|
|
||||||
if (result.metadata) {
|
try {
|
||||||
// Check architecture from metadata
|
// Handle validation differently for model files vs mmproj files
|
||||||
const architecture =
|
if (fileType === 'model') {
|
||||||
result.metadata.metadata?.['general.architecture']
|
// For model files, use the standard validateGgufFile method
|
||||||
|
if (typeof serviceHub.models().validateGgufFile === 'function') {
|
||||||
|
const result = await serviceHub.models().validateGgufFile(filePath)
|
||||||
|
|
||||||
// Extract baseName and use it as model name if available
|
if (result.metadata) {
|
||||||
const baseName = result.metadata.metadata?.['general.basename']
|
// Check architecture from metadata
|
||||||
|
const architecture =
|
||||||
|
result.metadata.metadata?.['general.architecture']
|
||||||
|
|
||||||
if (baseName) {
|
// Extract baseName and use it as model name if available
|
||||||
setModelName(baseName)
|
const baseName = result.metadata.metadata?.['general.basename']
|
||||||
|
|
||||||
|
if (baseName) {
|
||||||
|
setModelName(baseName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model files should NOT be clip
|
||||||
|
if (architecture === 'clip') {
|
||||||
|
const errorMessage =
|
||||||
|
'This model has CLIP architecture and cannot be imported as a text generation model. CLIP models are designed for vision tasks and require different handling.'
|
||||||
|
setValidationError(errorMessage)
|
||||||
|
console.error(
|
||||||
|
'CLIP architecture detected in model file:',
|
||||||
|
architecture
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model files should NOT be clip
|
if (!result.isValid) {
|
||||||
if (architecture === 'clip') {
|
setValidationError(result.error || 'Model validation failed')
|
||||||
const errorMessage =
|
console.error('Model validation failed:', result.error)
|
||||||
'This model has CLIP architecture and cannot be imported as a text generation model. CLIP models are designed for vision tasks and require different handling.'
|
}
|
||||||
setValidationError(errorMessage)
|
}
|
||||||
|
} else {
|
||||||
|
// For mmproj files, we need to manually validate since validateGgufFile rejects CLIP models
|
||||||
|
try {
|
||||||
|
// Import the readGgufMetadata function directly from Tauri
|
||||||
|
const { invoke } = await import('@tauri-apps/api/core')
|
||||||
|
|
||||||
|
const metadata = await invoke(
|
||||||
|
'plugin:llamacpp|read_gguf_metadata',
|
||||||
|
{
|
||||||
|
path: filePath,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if architecture matches expected type
|
||||||
|
const architecture = (
|
||||||
|
metadata as { metadata?: Record<string, string> }
|
||||||
|
).metadata?.['general.architecture']
|
||||||
|
|
||||||
|
// Get general.baseName from metadata
|
||||||
|
const baseName = (metadata as { metadata?: Record<string, string> })
|
||||||
|
.metadata?.['general.basename']
|
||||||
|
|
||||||
|
// MMProj files MUST be clip
|
||||||
|
if (architecture !== 'clip') {
|
||||||
|
const errorMessage = `This MMProj file has "${architecture}" architecture but should have "clip" architecture. MMProj files must be CLIP models for vision processing.`
|
||||||
|
setMmprojValidationError(errorMessage)
|
||||||
console.error(
|
console.error(
|
||||||
'CLIP architecture detected in model file:',
|
'Non-CLIP architecture detected in mmproj file:',
|
||||||
architecture
|
architecture
|
||||||
)
|
)
|
||||||
|
} else if (
|
||||||
|
baseName &&
|
||||||
|
modelName &&
|
||||||
|
!modelName.toLowerCase().includes(baseName.toLowerCase()) &&
|
||||||
|
!baseName.toLowerCase().includes(modelName.toLowerCase())
|
||||||
|
) {
|
||||||
|
// Validate that baseName and model name are compatible (one should contain the other)
|
||||||
|
const errorMessage = `MMProj file baseName "${baseName}" does not match model name "${modelName}". The MMProj file should be compatible with the selected model.`
|
||||||
|
setMmprojValidationError(errorMessage)
|
||||||
|
console.error('BaseName mismatch in mmproj file:', {
|
||||||
|
baseName,
|
||||||
|
modelName,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
} catch (directError) {
|
||||||
|
console.error(
|
||||||
if (!result.isValid) {
|
'Failed to validate mmproj file directly:',
|
||||||
setValidationError(result.error || 'Model validation failed')
|
directError
|
||||||
console.error('Model validation failed:', result.error)
|
)
|
||||||
|
const errorMessage = `Failed to read MMProj metadata: ${
|
||||||
|
directError instanceof Error
|
||||||
|
? directError.message
|
||||||
|
: 'Unknown error'
|
||||||
|
}`
|
||||||
|
setMmprojValidationError(errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} catch (error) {
|
||||||
// For mmproj files, we need to manually validate since validateGgufFile rejects CLIP models
|
console.error(`Failed to validate ${fileType} file:`, error)
|
||||||
try {
|
const errorMessage = `Failed to read ${fileType} metadata: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
// Import the readGgufMetadata function directly from Tauri
|
|
||||||
const { invoke } = await import('@tauri-apps/api/core')
|
|
||||||
|
|
||||||
const metadata = await invoke('plugin:llamacpp|read_gguf_metadata', {
|
if (fileType === 'model') {
|
||||||
path: filePath,
|
setValidationError(errorMessage)
|
||||||
})
|
} else {
|
||||||
|
|
||||||
|
|
||||||
// Check if architecture matches expected type
|
|
||||||
const architecture = (
|
|
||||||
metadata as { metadata?: Record<string, string> }
|
|
||||||
).metadata?.['general.architecture']
|
|
||||||
|
|
||||||
// Get general.baseName from metadata
|
|
||||||
const baseName = (metadata as { metadata?: Record<string, string> })
|
|
||||||
.metadata?.['general.basename']
|
|
||||||
|
|
||||||
// MMProj files MUST be clip
|
|
||||||
if (architecture !== 'clip') {
|
|
||||||
const errorMessage = `This MMProj file has "${architecture}" architecture but should have "clip" architecture. MMProj files must be CLIP models for vision processing.`
|
|
||||||
setMmprojValidationError(errorMessage)
|
|
||||||
console.error(
|
|
||||||
'Non-CLIP architecture detected in mmproj file:',
|
|
||||||
architecture
|
|
||||||
)
|
|
||||||
} else if (
|
|
||||||
baseName &&
|
|
||||||
modelName &&
|
|
||||||
!modelName.toLowerCase().includes(baseName.toLowerCase()) &&
|
|
||||||
!baseName.toLowerCase().includes(modelName.toLowerCase())
|
|
||||||
) {
|
|
||||||
// Validate that baseName and model name are compatible (one should contain the other)
|
|
||||||
const errorMessage = `MMProj file baseName "${baseName}" does not match model name "${modelName}". The MMProj file should be compatible with the selected model.`
|
|
||||||
setMmprojValidationError(errorMessage)
|
|
||||||
console.error('BaseName mismatch in mmproj file:', {
|
|
||||||
baseName,
|
|
||||||
modelName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (directError) {
|
|
||||||
console.error('Failed to validate mmproj file directly:', directError)
|
|
||||||
const errorMessage = `Failed to read MMProj metadata: ${
|
|
||||||
directError instanceof Error ? directError.message : 'Unknown error'
|
|
||||||
}`
|
|
||||||
setMmprojValidationError(errorMessage)
|
setMmprojValidationError(errorMessage)
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (fileType === 'model') {
|
||||||
|
setIsValidating(false)
|
||||||
|
} else {
|
||||||
|
setIsValidatingMmproj(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.error(`Failed to validate ${fileType} file:`, error)
|
[modelName, serviceHub]
|
||||||
const errorMessage = `Failed to read ${fileType} metadata: ${error instanceof Error ? error.message : 'Unknown error'}`
|
)
|
||||||
|
|
||||||
if (fileType === 'model') {
|
const validateModelFile = useCallback(
|
||||||
setValidationError(errorMessage)
|
async (filePath: string): Promise<void> => {
|
||||||
} else {
|
await validateGgufFile(filePath, 'model')
|
||||||
setMmprojValidationError(errorMessage)
|
},
|
||||||
}
|
[validateGgufFile]
|
||||||
} finally {
|
)
|
||||||
if (fileType === 'model') {
|
|
||||||
setIsValidating(false)
|
|
||||||
} else {
|
|
||||||
setIsValidatingMmproj(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [modelName, serviceHub])
|
|
||||||
|
|
||||||
const validateModelFile = useCallback(async (filePath: string): Promise<void> => {
|
const validateMmprojFile = useCallback(
|
||||||
await validateGgufFile(filePath, 'model')
|
async (filePath: string): Promise<void> => {
|
||||||
}, [validateGgufFile])
|
await validateGgufFile(filePath, 'mmproj')
|
||||||
|
},
|
||||||
const validateMmprojFile = useCallback(async (filePath: string): Promise<void> => {
|
[validateGgufFile]
|
||||||
await validateGgufFile(filePath, 'mmproj')
|
)
|
||||||
}, [validateGgufFile])
|
|
||||||
|
|
||||||
const handleFileSelect = async (type: 'model' | 'mmproj') => {
|
const handleFileSelect = async (type: 'model' | 'mmproj') => {
|
||||||
const selectedFile = await serviceHub.dialog().open({
|
const selectedFile = await serviceHub.dialog().open({
|
||||||
@ -291,7 +304,11 @@ export const ImportVisionModelDialog = ({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent
|
||||||
|
onInteractOutside={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
Import Model
|
Import Model
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user