Merge pull request #6233 from menloresearch/fix/import-model

fix: improve ux import model
This commit is contained in:
Faisal Amir 2025-08-20 09:03:33 +07:00 committed by GitHub
commit 6203a93325
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 126 additions and 53 deletions

55
src-tauri/Cargo.lock generated
View File

@ -854,8 +854,18 @@ version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.20.11",
"darling_macro 0.20.11",
]
[[package]]
name = "darling"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08440b3dd222c3d0433e63e097463969485f112baff337dfdaca043a0d760570"
dependencies = [
"darling_core 0.21.2",
"darling_macro 0.21.2",
]
[[package]]
@ -872,13 +882,38 @@ dependencies = [
"syn 2.0.104",
]
[[package]]
name = "darling_core"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25b7912bc28a04ab1b7715a68ea03aaa15662b43a1a4b2c480531fd19f8bf7e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.104",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"darling_core 0.20.11",
"quote",
"syn 2.0.104",
]
[[package]]
name = "darling_macro"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce154b9bea7fb0c8e8326e62d00354000c36e79770ff21b8c84e3aa267d9d531"
dependencies = [
"darling_core 0.21.2",
"quote",
"syn 2.0.104",
]
@ -3984,8 +4019,8 @@ dependencies = [
[[package]]
name = "rmcp"
version = "0.2.1"
source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3196c95f1dfafbffbdcdd6d365c94969ac975e6a#3196c95f1dfafbffbdcdd6d365c94969ac975e6a"
version = "0.5.0"
source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=209dbac50f51737ad953c3a2c8e28f3619b6c277#209dbac50f51737ad953c3a2c8e28f3619b6c277"
dependencies = [
"base64 0.22.1",
"chrono",
@ -4010,10 +4045,10 @@ dependencies = [
[[package]]
name = "rmcp-macros"
version = "0.2.1"
source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3196c95f1dfafbffbdcdd6d365c94969ac975e6a#3196c95f1dfafbffbdcdd6d365c94969ac975e6a"
version = "0.5.0"
source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=209dbac50f51737ad953c3a2c8e28f3619b6c277#209dbac50f51737ad953c3a2c8e28f3619b6c277"
dependencies = [
"darling",
"darling 0.21.2",
"proc-macro2",
"quote",
"serde_json",
@ -4408,7 +4443,7 @@ version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
dependencies = [
"darling",
"darling 0.20.11",
"proc-macro2",
"quote",
"syn 2.0.104",
@ -6868,7 +6903,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e"
dependencies = [
"darling",
"darling 0.20.11",
"proc-macro2",
"quote",
"syn 2.0.104",

View File

@ -79,6 +79,7 @@ function ProviderDetail() {
const [activeModels, setActiveModels] = useState<string[]>([])
const [loadingModels, setLoadingModels] = useState<string[]>([])
const [refreshingModels, setRefreshingModels] = useState(false)
const [importingModel, setImportingModel] = useState(false)
const { providerName } = useParams({ from: Route.id })
const { getProviderByName, setProviders, updateProvider } = useModelProvider()
const provider = getProviderByName(providerName)
@ -95,6 +96,72 @@ function ProviderDetail() {
!setting.controller_props.value)
)
const handleImportModel = async () => {
if (!provider) {
return
}
setImportingModel(true)
const selectedFile = await open({
multiple: false,
directory: false,
filters: [
{
name: 'GGUF',
extensions: ['gguf'],
},
],
})
// If the dialog returns a file path, extract just the file name
const fileName =
typeof selectedFile === 'string'
? selectedFile
.split(/[\\/]/)
.pop()
?.replace(/\s/g, '-')
: undefined
if (selectedFile && fileName) {
// Check if model already exists
const modelExists = provider.models.some(
(model) => model.name === fileName
)
if (modelExists) {
toast.error('Model already exists', {
description: `${fileName} already imported`,
})
setImportingModel(false)
return
}
try {
await pullModel(fileName, selectedFile)
// Refresh the provider to update the models list
await getProviders().then(setProviders)
toast.success(t('providers:import'), {
id: `import-model-${provider.provider}`,
description: t(
'providers:importModelSuccess',
{ provider: fileName }
),
})
} catch (error) {
console.error(
t('providers:importModelError'),
error
)
toast.error(t('providers:importModelError'), {
description: error instanceof Error ? error.message : 'Unknown error occurred',
})
} finally {
setImportingModel(false)
}
} else {
setImportingModel(false)
}
}
useEffect(() => {
// Initial data fetch
getActiveModels().then((models) => setActiveModels(models || []))
@ -482,52 +549,23 @@ function ProviderDetail() {
variant="link"
size="sm"
className="hover:no-underline"
onClick={async () => {
const selectedFile = await open({
multiple: false,
directory: false,
filters: [
{
name: 'GGUF',
extensions: ['gguf'],
},
],
})
// If the dialog returns a file path, extract just the file name
const fileName =
typeof selectedFile === 'string'
? selectedFile.split(/[\\/]/).pop()
: undefined
if (selectedFile && fileName) {
try {
await pullModel(fileName, selectedFile)
} catch (error) {
console.error(
t('providers:importModelError'),
error
)
} finally {
// Refresh the provider to update the models list
getProviders().then(setProviders)
toast.success(t('providers:import'), {
id: `import-model-${provider.provider}`,
description: t(
'providers:importModelSuccess',
{ provider: provider.provider }
),
})
}
}
}}
disabled={importingModel}
onClick={handleImportModel}
>
<div className="cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out p-1.5 py-1 gap-1 -mr-2">
<IconFolderPlus
size={18}
className="text-main-view-fg/50"
/>
{importingModel ? (
<IconLoader
size={18}
className="text-main-view-fg/50 animate-spin"
/>
) : (
<IconFolderPlus
size={18}
className="text-main-view-fg/50"
/>
)}
<span className="text-main-view-fg/70">
{t('providers:import')}
{importingModel ? 'Importing...' : t('providers:import')}
</span>
</div>
</Button>