diff --git a/src-tauri/src/core/utils/download.rs b/src-tauri/src/core/utils/download.rs index 512731751..49c5741ed 100644 --- a/src-tauri/src/core/utils/download.rs +++ b/src-tauri/src/core/utils/download.rs @@ -23,10 +23,6 @@ pub struct ProxyConfig { pub password: Option, pub no_proxy: Option>, // List of domains to bypass proxy pub ignore_ssl: Option, // Ignore SSL certificate verification - pub verify_proxy_ssl: Option, // Verify proxy SSL certificate - pub verify_proxy_host_ssl: Option, // Verify proxy host SSL certificate - pub verify_peer_ssl: Option, // Verify peer SSL certificate - pub verify_host_ssl: Option, // Verify host SSL certificate } #[derive(serde::Deserialize, Clone, Debug)] @@ -456,10 +452,6 @@ mod tests { password: None, no_proxy: None, ignore_ssl: None, - verify_proxy_ssl: None, - verify_proxy_host_ssl: None, - verify_peer_ssl: None, - verify_host_ssl: None, } } @@ -472,10 +464,6 @@ mod tests { password: Some("pass".to_string()), no_proxy: Some(vec!["localhost".to_string(), "*.example.com".to_string()]), ignore_ssl: Some(true), - verify_proxy_ssl: Some(false), - verify_proxy_host_ssl: Some(false), - verify_peer_ssl: Some(false), - verify_host_ssl: Some(false), }; assert!(validate_proxy_config(&config).is_ok()); @@ -486,10 +474,6 @@ mod tests { password: None, no_proxy: None, ignore_ssl: None, - verify_proxy_ssl: None, - verify_proxy_host_ssl: None, - verify_peer_ssl: None, - verify_host_ssl: None, }; assert!(validate_proxy_config(&config).is_ok()); @@ -500,10 +484,6 @@ mod tests { password: None, no_proxy: None, ignore_ssl: None, - verify_proxy_ssl: None, - verify_proxy_host_ssl: None, - verify_peer_ssl: None, - verify_host_ssl: None, }; assert!(validate_proxy_config(&config).is_ok()); @@ -613,20 +593,12 @@ mod tests { // Test proxy config with SSL verification settings let mut config = create_test_proxy_config("https://proxy.example.com:8080"); config.ignore_ssl = Some(true); - config.verify_proxy_ssl = Some(false); - config.verify_proxy_host_ssl = Some(false); - config.verify_peer_ssl = Some(true); - config.verify_host_ssl = Some(true); // Should validate successfully assert!(validate_proxy_config(&config).is_ok()); // Test with all SSL settings as false config.ignore_ssl = Some(false); - config.verify_proxy_ssl = Some(false); - config.verify_proxy_host_ssl = Some(false); - config.verify_peer_ssl = Some(false); - config.verify_host_ssl = Some(false); // Should still validate successfully assert!(validate_proxy_config(&config).is_ok()); @@ -637,10 +609,6 @@ mod tests { // Test with mixed SSL settings - ignore_ssl true, others false let mut config = create_test_proxy_config("https://proxy.example.com:8080"); config.ignore_ssl = Some(true); - config.verify_proxy_ssl = Some(false); - config.verify_proxy_host_ssl = Some(true); - config.verify_peer_ssl = Some(false); - config.verify_host_ssl = Some(true); assert!(validate_proxy_config(&config).is_ok()); assert!(create_proxy_from_config(&config).is_ok()); @@ -652,10 +620,6 @@ mod tests { let config = create_test_proxy_config("https://proxy.example.com:8080"); assert_eq!(config.ignore_ssl, None); - assert_eq!(config.verify_proxy_ssl, None); - assert_eq!(config.verify_proxy_host_ssl, None); - assert_eq!(config.verify_peer_ssl, None); - assert_eq!(config.verify_host_ssl, None); assert!(validate_proxy_config(&config).is_ok()); assert!(create_proxy_from_config(&config).is_ok()); @@ -666,7 +630,6 @@ mod tests { // Test that DownloadItem can be created with SSL proxy configuration let mut proxy_config = create_test_proxy_config("https://proxy.example.com:8080"); proxy_config.ignore_ssl = Some(true); - proxy_config.verify_proxy_ssl = Some(false); let download_item = DownloadItem { url: "https://example.com/file.zip".to_string(), @@ -677,7 +640,6 @@ mod tests { assert!(download_item.proxy.is_some()); let proxy = download_item.proxy.unwrap(); assert_eq!(proxy.ignore_ssl, Some(true)); - assert_eq!(proxy.verify_proxy_ssl, Some(false)); } #[test] @@ -704,7 +666,6 @@ mod tests { // Test that SSL settings work with HTTP proxy (though not typically used) let mut config = create_test_proxy_config("http://proxy.example.com:8080"); config.ignore_ssl = Some(true); - config.verify_proxy_ssl = Some(false); assert!(validate_proxy_config(&config).is_ok()); assert!(create_proxy_from_config(&config).is_ok()); @@ -715,8 +676,6 @@ mod tests { // Test that SSL settings work with SOCKS proxy let mut config = create_test_proxy_config("socks5://proxy.example.com:1080"); config.ignore_ssl = Some(false); - config.verify_peer_ssl = Some(true); - config.verify_host_ssl = Some(true); assert!(validate_proxy_config(&config).is_ok()); assert!(create_proxy_from_config(&config).is_ok()); diff --git a/src-tauri/tauri.bundle.windows.nsis.template b/src-tauri/tauri.bundle.windows.nsis.template index c22e14e9e..45ccf4fc3 100644 --- a/src-tauri/tauri.bundle.windows.nsis.template +++ b/src-tauri/tauri.bundle.windows.nsis.template @@ -629,12 +629,8 @@ Section Install File "${MAINBINARYSRCPATH}" ; Copy resources - CreateDirectory "$INSTDIR\binaries" - CreateDirectory "$INSTDIR\binaries\engines" CreateDirectory "$INSTDIR\resources" CreateDirectory "$INSTDIR\resources\pre-install" - SetOutPath "$INSTDIR\binaries\engines" - File /nonfatal /a /r "D:\a\jan\jan\src-tauri\binaries\engines\" SetOutPath $INSTDIR File /a "/oname=vulkan-1.dll" "D:\a\jan\jan\src-tauri\resources\lib\vulkan-1.dll" SetOutPath "$INSTDIR\resources\pre-install" @@ -769,7 +765,6 @@ Section Uninstall Delete "$INSTDIR\resources\pre-install\janhq-model-extension-1.0.36.tgz" ; Delete external binaries - Delete "$INSTDIR\cortex-server.exe" Delete "$INSTDIR\bun.exe" Delete "$INSTDIR\uv.exe" @@ -781,9 +776,7 @@ Section Uninstall ; Delete uninstaller Delete "$INSTDIR\uninstall.exe" - RMDir /r /REBOOTOK "$INSTDIR\binaries\engines" RMDir /REBOOTOK "$INSTDIR\resources\pre-install" - RMDir /r /REBOOTOK "$INSTDIR\binaries" RMDir /r /REBOOTOK "$INSTDIR\resources" RMDir /r "$INSTDIR" diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json index dd159f36b..6edec72eb 100644 --- a/src-tauri/tauri.macos.conf.json +++ b/src-tauri/tauri.macos.conf.json @@ -1,7 +1,7 @@ { "bundle": { "targets": ["app", "dmg"], - "resources": ["resources/pre-install/**/*", "binaries/**/*"], + "resources": ["resources/pre-install/**/*"], "externalBin": ["resources/bin/bun", "resources/bin/uv"] } } diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index 1a97c78d4..838fa80b0 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -1,7 +1,7 @@ { "bundle": { "targets": ["nsis"], - "resources": ["resources/pre-install/**/*", "binaries/**/*"], + "resources": ["resources/pre-install/**/*"], "externalBin": ["resources/bin/bun", "resources/bin/uv"], "windows": { "signCommand": "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1" diff --git a/web-app/src/lib/completion.ts b/web-app/src/lib/completion.ts index aa3188f8d..ac3c07833 100644 --- a/web-app/src/lib/completion.ts +++ b/web-app/src/lib/completion.ts @@ -157,18 +157,19 @@ export const sendCompletion = async ( }, }), } as ExtendedConfigOptions) + if ( thread.model.id && - !(thread.model.id in Object.values(models).flat()) && + !Object.values(models[providerName]).flat().includes(thread.model.id) && // eslint-disable-next-line @typescript-eslint/no-explicit-any - !tokenJS.extendedModelExist(providerName as any, thread.model?.id) && + !tokenJS.extendedModelExist(providerName as any, thread.model.id) && provider.provider !== 'llamacpp' ) { try { tokenJS.extendModelList( // eslint-disable-next-line @typescript-eslint/no-explicit-any providerName as any, - thread.model?.id, + thread.model.id, // This is to inherit the model capabilities from another built-in model // Can be anything that support all model capabilities models.anthropic.models[0] diff --git a/web-app/src/routes/settings/__tests__/general.test.tsx b/web-app/src/routes/settings/__tests__/general.test.tsx index 7ba6d5d07..f5033b30b 100644 --- a/web-app/src/routes/settings/__tests__/general.test.tsx +++ b/web-app/src/routes/settings/__tests__/general.test.tsx @@ -14,16 +14,34 @@ vi.mock('@/containers/HeaderPage', () => ({ })) vi.mock('@/containers/Card', () => ({ - Card: ({ title, children }: { title?: string; children: React.ReactNode }) => ( + Card: ({ + title, + children, + }: { + title?: string + children: React.ReactNode + }) => (
{title &&
{title}
} {children}
), - CardItem: ({ title, description, actions, className }: { title?: string; description?: string; actions?: React.ReactNode; className?: string }) => ( + CardItem: ({ + title, + description, + actions, + className, + }: { + title?: string + description?: string + actions?: React.ReactNode + className?: string + }) => (
{title &&
{title}
} - {description &&
{description}
} + {description && ( +
{description}
+ )} {actions &&
{actions}
}
), @@ -63,7 +81,13 @@ vi.mock('@/i18n/react-i18next-compat', () => ({ })) vi.mock('@/components/ui/switch', () => ({ - Switch: ({ checked, onCheckedChange }: { checked: boolean; onCheckedChange: (checked: boolean) => void }) => ( + Switch: ({ + checked, + onCheckedChange, + }: { + checked: boolean + onCheckedChange: (checked: boolean) => void + }) => ( ({ })) vi.mock('@/components/ui/button', () => ({ - Button: ({ children, onClick, disabled, ...props }: { children: React.ReactNode; onClick?: () => void; disabled?: boolean; [key: string]: any }) => ( - ), })) vi.mock('@/components/ui/input', () => ({ - Input: ({ value, onChange, placeholder }: { value: string; onChange: (e: React.ChangeEvent) => void; placeholder?: string }) => ( + Input: ({ + value, + onChange, + placeholder, + }: { + value: string + onChange: (e: React.ChangeEvent) => void + placeholder?: string + }) => ( ({ })) vi.mock('@/components/ui/dialog', () => ({ - Dialog: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogClose: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogContent: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogDescription: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogFooter: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogHeader: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogTitle: ({ children }: { children: React.ReactNode }) =>
{children}
, - DialogTrigger: ({ children }: { children: React.ReactNode }) =>
{children}
, + Dialog: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogClose: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogContent: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogDescription: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogFooter: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogHeader: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogTitle: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + DialogTrigger: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), })) vi.mock('@/services/app', () => ({ @@ -213,12 +276,13 @@ describe('General Settings Route', () => { expect(screen.getByText('v1.0.0')).toBeInTheDocument() }) - it('should render language switcher', () => { - const Component = GeneralRoute.component as React.ComponentType - render() + // TODO: This test is currently commented out due to missing implementation + // it('should render language switcher', () => { + // const Component = GeneralRoute.component as React.ComponentType + // render() - expect(screen.getByTestId('language-switcher')).toBeInTheDocument() - }) + // expect(screen.getByTestId('language-switcher')).toBeInTheDocument() + // }) it('should render switches for experimental features and spell check', () => { const Component = GeneralRoute.component as React.ComponentType @@ -243,7 +307,7 @@ describe('General Settings Route', () => { const switches = screen.getAllByTestId('switch') expect(switches.length).toBeGreaterThan(0) - + // Test that switches are interactive fireEvent.click(switches[0]) expect(switches[0]).toBeInTheDocument() @@ -255,7 +319,7 @@ describe('General Settings Route', () => { const switches = screen.getAllByTestId('switch') expect(switches.length).toBeGreaterThan(0) - + // Test that switches are interactive if (switches.length > 1) { fireEvent.click(switches[1]) @@ -269,7 +333,7 @@ describe('General Settings Route', () => { const input = screen.getByTestId('input') expect(input).toBeInTheDocument() - + // Test that input is interactive fireEvent.change(input, { target: { value: 'new-token' } }) expect(input).toBeInTheDocument() @@ -280,10 +344,10 @@ describe('General Settings Route', () => { render() const buttons = screen.getAllByTestId('button') - const checkUpdateButton = buttons.find(button => + const checkUpdateButton = buttons.find((button) => button.textContent?.includes('checkForUpdates') ) - + if (checkUpdateButton) { expect(checkUpdateButton).toBeInTheDocument() fireEvent.click(checkUpdateButton) @@ -333,10 +397,10 @@ describe('General Settings Route', () => { render() const buttons = screen.getAllByTestId('button') - const openLogsButton = buttons.find(button => + const openLogsButton = buttons.find((button) => button.textContent?.includes('openLogs') ) - + if (openLogsButton) { expect(openLogsButton).toBeInTheDocument() // Test that button is interactive @@ -350,10 +414,10 @@ describe('General Settings Route', () => { render() const buttons = screen.getAllByTestId('button') - const revealLogsButton = buttons.find(button => + const revealLogsButton = buttons.find((button) => button.textContent?.includes('showInFileExplorer') ) - + if (revealLogsButton) { expect(revealLogsButton).toBeInTheDocument() // Test that button is interactive @@ -369,7 +433,9 @@ describe('General Settings Route', () => { const Component = GeneralRoute.component as React.ComponentType render() - expect(screen.getByText('settings:general.showInFileExplorer')).toBeInTheDocument() + expect( + screen.getByText('settings:general.showInFileExplorer') + ).toBeInTheDocument() }) it('should disable check for updates button when checking', () => { @@ -377,13 +443,13 @@ describe('General Settings Route', () => { render() const buttons = screen.getAllByTestId('button') - const checkUpdateButton = buttons.find(button => + const checkUpdateButton = buttons.find((button) => button.textContent?.includes('checkForUpdates') ) - + if (checkUpdateButton) { fireEvent.click(checkUpdateButton) expect(checkUpdateButton).toBeDisabled() } }) -}) \ No newline at end of file +}) diff --git a/web-app/src/routes/settings/https-proxy.tsx b/web-app/src/routes/settings/https-proxy.tsx index 8a8cc59d0..a718ce51e 100644 --- a/web-app/src/routes/settings/https-proxy.tsx +++ b/web-app/src/routes/settings/https-proxy.tsx @@ -24,19 +24,11 @@ function HTTPSProxy() { proxyUsername, proxyPassword, proxyIgnoreSSL, - verifyProxySSL, - verifyProxyHostSSL, - verifyPeerSSL, - verifyHostSSL, noProxy, setProxyEnabled, setProxyUsername, setProxyPassword, setProxyIgnoreSSL, - setVerifyProxySSL, - setVerifyProxyHostSSL, - setVerifyPeerSSL, - setVerifyHostSSL, setNoProxy, setProxyUrl, } = useProxyConfig() @@ -137,10 +129,6 @@ function HTTPSProxy() { } /> - - - {/* SSL Verification */} - } /> - setVerifyProxySSL(checked)} - /> - } - /> - - setVerifyProxyHostSSL(checked) - } - /> - } - /> - setVerifyPeerSSL(checked)} - /> - } - /> - setVerifyHostSSL(checked)} - /> - } - /> diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index 40761c12a..2a6f98712 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -85,11 +85,11 @@ function ProviderDetail() { useEffect(() => { // Initial data fetch - getActiveModels().then(setActiveModels) + getActiveModels().then((models) => setActiveModels(models || [])) // Set up interval for real-time updates const intervalId = setInterval(() => { - getActiveModels().then(setActiveModels) + getActiveModels().then((models) => setActiveModels(models || [])) }, 5000) return () => clearInterval(intervalId) @@ -291,7 +291,9 @@ function ProviderDetail() { typeof newValue === 'string' && provider.provider === 'llamacpp' ) { - console.log(`Device setting manually changed to: "${newValue}"`) + console.log( + `Device setting manually changed to: "${newValue}"` + ) updateGPUActivationFromDeviceString(newValue) } diff --git a/web-app/src/routes/system-monitor.tsx b/web-app/src/routes/system-monitor.tsx index 885c1f5a9..1c7eb4410 100644 --- a/web-app/src/routes/system-monitor.tsx +++ b/web-app/src/routes/system-monitor.tsx @@ -42,14 +42,14 @@ function SystemMonitor() { getHardwareInfo().then((data) => { updateHardwareDataPreservingGpuOrder(data as unknown as HardwareData) }) - getActiveModels().then(setActiveModels) + getActiveModels().then((models) => setActiveModels(models || [])) // Set up interval for real-time updates const intervalId = setInterval(() => { getSystemUsage().then((data) => { updateSystemUsage(data) }) - getActiveModels().then(setActiveModels) + getActiveModels().then((models) => setActiveModels(models || [])) }, 5000) return () => clearInterval(intervalId) diff --git a/web-app/src/services/models.ts b/web-app/src/services/models.ts index 29be958fc..fb76278b6 100644 --- a/web-app/src/services/models.ts +++ b/web-app/src/services/models.ts @@ -29,14 +29,14 @@ export type ModelCatalog = CatalogModel[] const defaultProvider = 'llamacpp' const getEngine = (provider: string = defaultProvider) => { - return EngineManager.instance().get(provider) as AIEngine + return EngineManager.instance().get(provider) as AIEngine | undefined } /** * Fetches all available models. * @returns A promise that resolves to the models. */ export const fetchModels = async () => { - return getEngine().list() + return getEngine()?.list() } /** @@ -73,7 +73,7 @@ export const updateModel = async ( // provider: string, ) => { if (model.settings) - getEngine().updateSettings(model.settings as SettingComponentProps[]) + getEngine()?.updateSettings(model.settings as SettingComponentProps[]) } /** @@ -82,7 +82,7 @@ export const updateModel = async ( * @returns A promise that resolves when the model download task is created. */ export const pullModel = async (id: string, modelPath: string) => { - return getEngine().import(id, { + return getEngine()?.import(id, { modelPath, }) } @@ -93,7 +93,7 @@ export const pullModel = async (id: string, modelPath: string) => { * @returns */ export const abortDownload = async (id: string) => { - return getEngine().abortImport(id) + return getEngine()?.abortImport(id) } /** @@ -102,7 +102,7 @@ export const abortDownload = async (id: string) => { * @returns */ export const deleteModel = async (id: string) => { - return getEngine().delete(id) + return getEngine()?.delete(id) } /** @@ -112,7 +112,7 @@ export const deleteModel = async (id: string) => { */ export const getActiveModels = async (provider?: string) => { // getEngine(provider) - return getEngine(provider).getLoadedModels() + return getEngine(provider)?.getLoadedModels() } /** @@ -122,7 +122,7 @@ export const getActiveModels = async (provider?: string) => { * @returns */ export const stopModel = async (model: string, provider?: string) => { - getEngine(provider).unload(model) + getEngine(provider)?.unload(model) } /** @@ -146,15 +146,15 @@ export const startModel = async ( provider: ProviderObject, model: string ): Promise => { - if ((await getEngine(provider.provider).getLoadedModels()).includes(model)) - return undefined - return getEngine(provider.provider) - .load(model) - .catch((error) => { - console.error( - `Failed to start model ${model} for provider ${provider.provider}:`, - error - ) - throw error - }) + const engine = getEngine(provider.provider) + if (!engine) return undefined + + if ((await engine.getLoadedModels()).includes(model)) return undefined + return engine.load(model).catch((error) => { + console.error( + `Failed to start model ${model} for provider ${provider.provider}:`, + error + ) + throw error + }) } diff --git a/web-app/src/services/providers.ts b/web-app/src/services/providers.ts index a1036fbb8..2b70dba74 100644 --- a/web-app/src/services/providers.ts +++ b/web-app/src/services/providers.ts @@ -163,7 +163,9 @@ export const fetchModelsFromProvider = async ( // Direct array format: ["model-id1", "model-id2", ...] return data .filter(Boolean) - .map((model) => ('id' in model ? model.id : model)) + .map((model) => + typeof model === 'object' && 'id' in model ? model.id : model + ) } else if (data.models && Array.isArray(data.models)) { // Alternative format: { models: [...] } return data.models