diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fc6bfd3017..32638bc56d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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", diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index 6300b4f481..fa57ca497b 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -79,6 +79,7 @@ function ProviderDetail() { const [activeModels, setActiveModels] = useState([]) const [loadingModels, setLoadingModels] = useState([]) 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} >
- + {importingModel ? ( + + ) : ( + + )} - {t('providers:import')} + {importingModel ? 'Importing...' : t('providers:import')}