diff --git a/web-app/index.html b/web-app/index.html index fc264d096..f59835ecb 100644 --- a/web-app/index.html +++ b/web-app/index.html @@ -1,11 +1,24 @@ - + - - + + - + Jan diff --git a/web-app/src/containers/LeftPanel.tsx b/web-app/src/containers/LeftPanel.tsx index da596dd4a..a6a31218a 100644 --- a/web-app/src/containers/LeftPanel.tsx +++ b/web-app/src/containers/LeftPanel.tsx @@ -122,7 +122,7 @@ const LeftPanel = () => { ) { if (currentIsSmallScreen && open) { setLeftPanel(false) - } else if(!open) { + } else if (!open) { setLeftPanel(true) } prevScreenSizeRef.current = currentIsSmallScreen @@ -141,7 +141,7 @@ const LeftPanel = () => { return () => { window.removeEventListener('resize', handleResize) } - }, [setLeftPanel]) + }, [open, setLeftPanel]) const currentPath = useRouterState({ select: (state) => state.location.pathname, @@ -184,7 +184,7 @@ const LeftPanel = () => { return ( <> {/* Backdrop overlay for small screens */} - {isSmallScreen && open && ( + {isSmallScreen && open && !IS_IOS && !IS_ANDROID && (
{ @@ -207,7 +207,7 @@ const LeftPanel = () => { isResizableContext && 'h-full w-full', // Small screen context: fixed positioning and styling isSmallScreen && - 'fixed h-[calc(100%-16px)] bg-app z-50 rounded-sm border border-left-panel-fg/10 m-2 px-1 w-48', + 'fixed h-full pb-[calc(env(safe-area-inset-bottom)+env(safe-area-inset-top))] bg-main-view z-50 md:border border-left-panel-fg/10 px-1 w-full md:w-48', // Default context: original styling !isResizableContext && !isSmallScreen && @@ -266,7 +266,8 @@ const LeftPanel = () => {
0 || localDownloadingModels.size > 0 + Object.keys(downloads).length > 0 || + localDownloadingModels.size > 0 ? 'h-[calc(100%-200px)]' : 'h-[calc(100%-140px)]' )} @@ -379,7 +380,9 @@ const LeftPanel = () => { - +
diff --git a/web-app/src/containers/SettingsMenu.tsx b/web-app/src/containers/SettingsMenu.tsx index da0e94870..78389233d 100644 --- a/web-app/src/containers/SettingsMenu.tsx +++ b/web-app/src/containers/SettingsMenu.tsx @@ -30,12 +30,15 @@ const SettingsMenu = () => { // On web: exclude llamacpp provider as it's not available const activeProviders = providers.filter((provider) => { if (!provider.active) return false - + // On web version, hide llamacpp provider - if (!PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && provider.provider === 'llama.cpp') { + if ( + !PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && + provider.provider === 'llama.cpp' + ) { return false } - + return true }) @@ -92,7 +95,7 @@ const SettingsMenu = () => { title: 'common:keyboardShortcuts', route: route.settings.shortcuts, hasSubMenu: false, - isEnabled: true, + isEnabled: PlatformFeatures[PlatformFeature.SHORTCUT], }, { title: 'common:hardware', @@ -137,7 +140,7 @@ const SettingsMenu = () => { return ( <> - )} -
- +
+ +
+ + {t(menu.title)} + + {menu.hasSubMenu && ( + + )} +
+ - {/* Sub-menu for model providers */} - {menu.hasSubMenu && expandedProviders && ( -
- {activeProviders.map((provider) => { - const isActive = matches.some( - (match) => - match.routeId === '/settings/providers/$providerName' && - 'providerName' in match.params && - match.params.providerName === provider.provider - ) + {/* Sub-menu for model providers */} + {menu.hasSubMenu && expandedProviders && ( +
+ {activeProviders.map((provider) => { + const isActive = matches.some( + (match) => + match.routeId === + '/settings/providers/$providerName' && + 'providerName' in match.params && + match.params.providerName === provider.provider + ) - return ( -
- - )} -
+ ) + })} +
+ )} +
) })}
diff --git a/web-app/src/containers/SetupScreen.tsx b/web-app/src/containers/SetupScreen.tsx index 812ed6493..cdf0020d8 100644 --- a/web-app/src/containers/SetupScreen.tsx +++ b/web-app/src/containers/SetupScreen.tsx @@ -6,6 +6,8 @@ import HeaderPage from './HeaderPage' import { isProd } from '@/lib/version' import { useTranslation } from '@/i18n/react-i18next-compat' import { localStorageKey } from '@/constants/localStorage' +import { PlatformFeatures } from '@/lib/platform/const' +import { PlatformFeature } from '@/lib/platform' function SetupScreen() { const { t } = useTranslation() @@ -21,7 +23,7 @@ function SetupScreen() {
-
+

{t('setup:welcome')} @@ -31,22 +33,24 @@ function SetupScreen() {

- -
-

- {t('setup:localModel')} -

-
- - } - >
+ {PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && ( + +
+

+ {t('setup:localModel')} +

+
+ + } + /> + )} } - > + />
diff --git a/web-app/src/containers/analytics/PromptAnalytic.tsx b/web-app/src/containers/analytics/PromptAnalytic.tsx index 25e4e8d8c..425492a31 100644 --- a/web-app/src/containers/analytics/PromptAnalytic.tsx +++ b/web-app/src/containers/analytics/PromptAnalytic.tsx @@ -21,7 +21,7 @@ export function PromptAnalytic() { } return ( -
+

@@ -45,7 +45,9 @@ export function PromptAnalytic() { > {t('deny')} - +

) diff --git a/web-app/src/index.css b/web-app/src/index.css index d8ae284e9..1c2e385d5 100644 --- a/web-app/src/index.css +++ b/web-app/src/index.css @@ -56,6 +56,13 @@ @layer base { body { @apply overflow-hidden; + background-color: white; + min-height: 100vh; + min-height: -webkit-fill-available; + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); ::-webkit-scrollbar { width: 6px; height: 6px; diff --git a/web-app/src/lib/platform/const.ts b/web-app/src/lib/platform/const.ts index ac623bd79..ee9632aab 100644 --- a/web-app/src/lib/platform/const.ts +++ b/web-app/src/lib/platform/const.ts @@ -4,7 +4,7 @@ */ import { PlatformFeature } from './types' -import { isPlatformTauri } from './utils' +import { isPlatformTauri, isPlatformIOS, isPlatformAndroid } from './utils' /** * Platform Features Configuration @@ -12,28 +12,35 @@ import { isPlatformTauri } from './utils' */ export const PlatformFeatures: Record = { // Hardware monitoring and GPU usage - [PlatformFeature.HARDWARE_MONITORING]: isPlatformTauri(), + [PlatformFeature.HARDWARE_MONITORING]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // Local model inference (llama.cpp) - [PlatformFeature.LOCAL_INFERENCE]: isPlatformTauri(), + [PlatformFeature.LOCAL_INFERENCE]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // Local API server - [PlatformFeature.LOCAL_API_SERVER]: isPlatformTauri(), + [PlatformFeature.LOCAL_API_SERVER]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // Hub/model downloads - [PlatformFeature.MODEL_HUB]: isPlatformTauri(), + [PlatformFeature.MODEL_HUB]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // System integrations (logs, file explorer, etc.) - [PlatformFeature.SYSTEM_INTEGRATIONS]: isPlatformTauri(), + [PlatformFeature.SYSTEM_INTEGRATIONS]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // HTTPS proxy - [PlatformFeature.HTTPS_PROXY]: isPlatformTauri(), - + [PlatformFeature.HTTPS_PROXY]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), + // Default model providers (OpenAI, Anthropic, etc.) - disabled for web-only Jan builds [PlatformFeature.DEFAULT_PROVIDERS]: isPlatformTauri(), // Analytics and telemetry - disabled for web - [PlatformFeature.ANALYTICS]: isPlatformTauri(), + [PlatformFeature.ANALYTICS]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // Web-specific automatic model selection from jan provider - enabled for web only [PlatformFeature.WEB_AUTO_MODEL_SELECTION]: !isPlatformTauri(), @@ -45,10 +52,12 @@ export const PlatformFeatures: Record = { [PlatformFeature.MCP_AUTO_APPROVE_TOOLS]: !isPlatformTauri(), // MCP servers settings page - disabled for web - [PlatformFeature.MCP_SERVERS_SETTINGS]: isPlatformTauri(), + [PlatformFeature.MCP_SERVERS_SETTINGS]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // Extensions settings page - disabled for web - [PlatformFeature.EXTENSIONS_SETTINGS]: isPlatformTauri(), + [PlatformFeature.EXTENSIONS_SETTINGS]: + isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), // Assistant functionality - disabled for web [PlatformFeature.ASSISTANTS]: isPlatformTauri(), @@ -60,5 +69,9 @@ export const PlatformFeatures: Record = { [PlatformFeature.GOOGLE_ANALYTICS]: !isPlatformTauri(), // Alternate shortcut bindings - enabled for web only (to avoid browser conflicts) - [PlatformFeature.ALTERNATE_SHORTCUT_BINDINGS]: !isPlatformTauri(), -} \ No newline at end of file + [PlatformFeature.ALTERNATE_SHORTCUT_BINDINGS]: + !isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(), + + // Shortcut + [PlatformFeature.SHORTCUT]: !isPlatformIOS() && !isPlatformAndroid(), +} diff --git a/web-app/src/lib/platform/types.ts b/web-app/src/lib/platform/types.ts index a644c09bd..1c1c4e5a6 100644 --- a/web-app/src/lib/platform/types.ts +++ b/web-app/src/lib/platform/types.ts @@ -6,7 +6,7 @@ /** * Supported platforms */ -export type Platform = 'tauri' | 'web' +export type Platform = 'tauri' | 'web' | 'ios' | 'android' /** * Platform Feature Enum @@ -16,6 +16,8 @@ export enum PlatformFeature { // Hardware monitoring and GPU usage HARDWARE_MONITORING = 'hardwareMonitoring', + SHORTCUT = 'shortcut', + // Local model inference (llama.cpp) LOCAL_INFERENCE = 'localInference', @@ -30,16 +32,16 @@ export enum PlatformFeature { // HTTPS proxy HTTPS_PROXY = 'httpsProxy', - + // Default model providers (OpenAI, Anthropic, etc.) DEFAULT_PROVIDERS = 'defaultProviders', - + // Analytics and telemetry ANALYTICS = 'analytics', - + // Web-specific automatic model selection from jan provider WEB_AUTO_MODEL_SELECTION = 'webAutoModelSelection', - + // Model provider settings page management MODEL_PROVIDER_SETTINGS = 'modelProviderSettings', diff --git a/web-app/src/lib/platform/utils.ts b/web-app/src/lib/platform/utils.ts index 9ef9183d9..cc905802a 100644 --- a/web-app/src/lib/platform/utils.ts +++ b/web-app/src/lib/platform/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Platform, PlatformFeature } from './types' declare const IS_WEB_APP: boolean @@ -12,7 +13,25 @@ export const isPlatformTauri = (): boolean => { return true } +export const isPlatformIOS = (): boolean => { + if (typeof navigator === 'undefined') return false + return ( + /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream + ) +} + +export const isPlatformAndroid = (): boolean => { + if (typeof navigator === 'undefined') return false + return /Android/.test(navigator.userAgent) +} + +export const isIOS = (): boolean => isPlatformIOS() + +export const isAndroid = (): boolean => isPlatformAndroid() + export const getCurrentPlatform = (): Platform => { + if (isPlatformIOS()) return 'ios' + if (isPlatformAndroid()) return 'android' return isPlatformTauri() ? 'tauri' : 'web' } diff --git a/web-app/src/routes/__root.tsx b/web-app/src/routes/__root.tsx index bc4422b84..f7e61c152 100644 --- a/web-app/src/routes/__root.tsx +++ b/web-app/src/routes/__root.tsx @@ -111,13 +111,17 @@ const AppLayout = () => { return ( - {PlatformFeatures[PlatformFeature.GOOGLE_ANALYTICS] && } + {PlatformFeatures[PlatformFeature.GOOGLE_ANALYTICS] && ( + + )}
{/* Fake absolute panel top to enable window drag */}
- {PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && } + {PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && ( + + )} {/* Use ResizablePanelGroup only on larger screens */} {!isSmallScreen && isLeftPanelOpen ? ( @@ -158,11 +162,11 @@ const AppLayout = () => { {/* Main content panel */}
-
+
diff --git a/web-app/src/routes/settings/appearance.tsx b/web-app/src/routes/settings/appearance.tsx index 3cba3eed5..ab4bd9768 100644 --- a/web-app/src/routes/settings/appearance.tsx +++ b/web-app/src/routes/settings/appearance.tsx @@ -31,7 +31,7 @@ function Appareances() { const { resetCodeBlockStyle } = useCodeblock() return ( -
+

{t('common:settings')}

diff --git a/web-app/src/routes/settings/general.tsx b/web-app/src/routes/settings/general.tsx index 687709710..c13d928e8 100644 --- a/web-app/src/routes/settings/general.tsx +++ b/web-app/src/routes/settings/general.tsx @@ -161,7 +161,7 @@ function General() { }, [t, checkForUpdate]) return ( -
+

{t('common:settings')}

@@ -181,28 +181,29 @@ function General() { } /> )} - {!AUTO_UPDATER_DISABLED && PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( - -
- {isCheckingUpdate - ? t('settings:general.checkingForUpdates') - : t('settings:general.checkForUpdates')} -
- - } - /> - )} + {!AUTO_UPDATER_DISABLED && + PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( + +
+ {isCheckingUpdate + ? t('settings:general.checkingForUpdates') + : t('settings:general.checkForUpdates')} +
+ + } + /> + )} } @@ -211,165 +212,173 @@ function General() { {/* Data folder - Desktop only */} {PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( - - - - {t('settings:dataFolder.appDataDesc', { - ns: 'settings', - })} -   - -
-
- + + + {t('settings:dataFolder.appDataDesc', { + ns: 'settings', + })} +   + +
+
+ + {janDataFolder} + +
+
- -
- - } - actions={ - <> - - {selectedNewPath && ( - { - setIsDialogOpen(open) - if (!open) { - setSelectedNewPath(null) + {t('settings:general.changeLocation')} +
+ + {selectedNewPath && ( + { + setIsDialogOpen(open) + if (!open) { + setSelectedNewPath(null) + } + }} + > +
+ + )} + + } + /> + + + - -
- } - /> -
+
+ + {openFileTitle()} +
+ +
+ } + /> + )} {/* Advanced - Desktop only */} {PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( - - - - - } - /> - + + + + + } + /> + )} {/* Other */} diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index efba6233c..f220ff24f 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -443,7 +443,7 @@ function ProviderDetail() { return ( <> -
+

{t('common:settings')}