diff --git a/.gitignore b/.gitignore index 20537a4..f0707c3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,18 +11,3 @@ Thumbs.db # Yarn .yarn/ .pnp.* - -t5xxl_fp8_e4m3fn.safetensors.aria2 -ae.safetensors -clip_l.safetensors -t5xxl_fp8_e4m3fn.safetensors -flux1-kontext-dev-Q3_K_S.gguf -gemma-3-4b-it.Q8_0.gguf -.webui_secret_key -chroma-unlocked-v29-Q3_K_L.gguf -Qwen-Image-Edit-2509-Q4_K_S.gguf -Qwen2.5-VL-7B-Instruct.mmproj-Q8_0.gguf -Qwen2.5-VL-7B-Instruct.Q4_K_S.gguf -qwen_image_vae.safetensors -flux1-kontext-dev-Q4_K_S.gguf -chroma-unlocked-v45-Q4_0.gguf diff --git a/package.json b/package.json index b0b139d..1bf762e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gerbil", "productName": "Gerbil", - "version": "1.9.0", + "version": "2.0.0", "description": "Run Large Language Models locally", "main": "out/main/index.js", "homepage": "./", @@ -40,11 +40,11 @@ "devDependencies": { "@eslint/js": "^9.39.1", "@types/node": "^24.10.1", - "@types/react": "^19.2.5", + "@types/react": "^19.2.6", "@types/react-dom": "^19.2.3", "@types/yauzl": "^2.10.3", - "@typescript-eslint/eslint-plugin": "^8.46.4", - "@typescript-eslint/parser": "^8.46.4", + "@typescript-eslint/eslint-plugin": "^8.47.0", + "@typescript-eslint/parser": "^8.47.0", "@vitejs/plugin-react": "^5.1.1", "cross-env": "^10.1.0", "electron": "^38.7.0", @@ -67,7 +67,7 @@ "dependencies": { "@codemirror/search": "^6.5.11", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.38.7", + "@codemirror/view": "^6.38.8", "@fontsource/inter": "^5.2.8", "@huggingface/gguf": "^0.3.2", "@mantine/core": "^8.3.8", @@ -75,7 +75,7 @@ "@uiw/react-codemirror": "^4.25.3", "electron-updater": "^6.6.2", "execa": "^9.6.0", - "lucide-react": "^0.553.0", + "lucide-react": "^0.554.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-error-boundary": "^6.0.0", diff --git a/src/components/screens/Launch/GeneralTab/index.tsx b/src/components/screens/Launch/GeneralTab/index.tsx index 58e9aa7..e10df56 100644 --- a/src/components/screens/Launch/GeneralTab/index.tsx +++ b/src/components/screens/Launch/GeneralTab/index.tsx @@ -44,6 +44,7 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => { onSelectFile={handleSelectModelFile} searchUrl="https://huggingface.co/models?pipeline_tag=text-generation&library=gguf&sort=trending" showAnalyze + paramType="model" />
diff --git a/src/components/screens/Launch/ImageGenerationTab.tsx b/src/components/screens/Launch/ImageGenerationTab.tsx index 6057431..b8f4107 100644 --- a/src/components/screens/Launch/ImageGenerationTab.tsx +++ b/src/components/screens/Launch/ImageGenerationTab.tsx @@ -77,15 +77,17 @@ export const ImageGenerationTab = () => { onSelectFile={handleSelectSdmodelFile} searchUrl="https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending" showAnalyze + paramType="sdmodel" /> { tooltip="CLIP-L text encoder model for text-image understanding." onChange={handleSdcliplChange} onSelectFile={handleSelectSdcliplFile} + paramType="sdclipl" /> { tooltip="CLIP-G text encoder model for enhanced text-image understanding." onChange={handleSdclipgChange} onSelectFile={handleSelectSdclipgFile} + paramType="sdclipg" /> { tooltip="PhotoMaker is a model that allows face cloning. Select a .safetensors PhotoMaker file to be loaded (SDXL only)." onChange={handleSdphotomakerChange} onSelectFile={handleSelectSdphotomakerFile} + paramType="sdphotomaker" /> { tooltip="Variational Autoencoder model for improved image quality." onChange={handleSdvaeChange} onSelectFile={handleSelectSdvaeFile} + paramType="sdvae" /> { tooltip="LoRa (Low-Rank Adaptation) file for customizing image generation. Select a .safetensors or .gguf LoRa file to be loaded. Should be unquantized." onChange={handleSdloraChange} onSelectFile={handleSelectSdloraFile} + paramType="sdlora" /> void; searchUrl?: string; showAnalyze?: boolean; + paramType: ModelParamType; } export const ModelFileField = ({ @@ -27,6 +36,7 @@ export const ModelFileField = ({ onSelectFile, searchUrl, showAnalyze = false, + paramType, }: ModelFileFieldProps) => { const validationState = getInputValidationState(value); const [analysisModalOpened, setAnalysisModalOpened] = useState(false); @@ -35,6 +45,26 @@ export const ModelFileField = ({ ); const [analysisLoading, setAnalysisLoading] = useState(false); const [analysisError, setAnalysisError] = useState(); + const [cachedModels, setCachedModels] = useState([]); + const combobox = useCombobox(); + + useEffect(() => { + (async () => { + try { + const models = + await window.electronAPI.kobold.getLocalModels(paramType); + setCachedModels(models); + } catch (error) { + logError('Failed to load cached models:', error as Error); + } + })(); + }, [paramType]); + + const options = cachedModels.map((model) => ( + + {model.author}/{model.model} + + )); const getHelperText = () => { if (validationState === 'neutral') return undefined; @@ -72,12 +102,37 @@ export const ModelFileField = ({
- onChange(event.currentTarget.value)} - error={validationState === 'invalid' ? getHelperText() : undefined} - /> + { + onChange(val); + combobox.closeDropdown(); + }} + > + + { + onChange(event.currentTarget.value); + combobox.openDropdown(); + }} + onFocus={() => combobox.openDropdown()} + onBlur={() => combobox.closeDropdown()} + error={ + validationState === 'invalid' ? getHelperText() : undefined + } + rightSection={} + rightSectionPointerEvents="none" + /> + + + {options.length > 0 && ( + + {options} + + )} +