Update .gitignore, refactor UI components, and enhance model download logic

- Added CLAUDE.local.md to .gitignore to prevent local configuration files from being tracked.
- Refactored the App component layout for improved responsiveness and user experience.
- Updated ModelDownloadProgress to include a new `isDownloaded` state for better visibility of download status.
- Simplified SettingsHome by commenting out unused functions and improving the organization of settings options.
- Adjusted PrivacyPanel to ensure analytics settings are correctly evaluated.
- Modified various page components to use `min-h-full` for consistent height management across the application.
This commit is contained in:
Steven Enamakel
2026-02-06 01:59:56 +05:30
parent af7ca87831
commit aeb9626db1
12 changed files with 140 additions and 140 deletions
+2
View File
@@ -38,3 +38,5 @@ src-tauri/runtime-skill-*
.ruff_cache
.kotlin
.cargo
CLAUDE.local.md
+1 -1
Submodule skills updated: c4f28c3cf6...21f64b6049
+3 -3
View File
@@ -22,13 +22,13 @@ function App() {
<AIProvider>
<SkillProvider>
<Router>
<div className="relative min-h-screen">
<div className="pointer-events-none fixed inset-x-0 top-0 flex justify-center z-50">
<div className="relative h-screen flex flex-col overflow-hidden">
<div className="pointer-events-none flex-shrink-0 flex justify-center z-50">
<div className="bg-black w-full px-3 py-1.5 text-[11px] uppercase tracking-[0.18em] text-white/40 text-center">
AlphaHuman is in early beta.
</div>
</div>
<div className="pt-7">
<div className="flex-1 overflow-y-auto">
<AppRoutes />
</div>
</div>
+3 -3
View File
@@ -12,7 +12,7 @@ const ModelDownloadProgress = ({
className = '',
showWhenLoaded = false,
}: ModelDownloadProgressProps) => {
const { isAvailable, isLoaded, isLoading, downloadProgress, error, ensureLoaded } =
const { isAvailable, isLoaded, isLoading, isDownloaded, downloadProgress, error, ensureLoaded } =
useModelStatus();
const [isMobile, setIsMobile] = useState(false);
@@ -63,8 +63,8 @@ const ModelDownloadProgress = ({
return null;
}
// Don't render if loaded and showWhenLoaded is false
if (isLoaded && !showWhenLoaded && !isLoading) {
// Hide when downloaded or loaded (nothing for the user to do)
if ((isDownloaded || isLoaded) && !showWhenLoaded && !isLoading) {
return null;
}
+62 -62
View File
@@ -13,10 +13,10 @@ const SettingsHome = () => {
closeSettings();
};
const handleViewEncryptionKey = () => {
// TODO: Show encryption key in a secure modal
console.log('View encryption key');
};
// const handleViewEncryptionKey = () => {
// // TODO: Show encryption key in a secure modal
// console.log('View encryption key');
// };
const handleDeleteAllData = () => {
// TODO: Show confirmation dialog and delete all data
@@ -64,63 +64,63 @@ const SettingsHome = () => {
onClick: () => navigateToSettings('privacy'),
dangerous: false,
},
{
id: 'profile',
title: 'Profile',
description: 'Update your profile information and preferences',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
),
onClick: () => navigateToSettings('profile'),
dangerous: false,
},
{
id: 'advanced',
title: 'Advanced',
description: 'Advanced configuration and developer options',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
),
onClick: () => navigateToSettings('advanced'),
dangerous: false,
},
{
id: 'encryption',
title: 'View Encryption Key',
description: 'Access your encryption key for backup purposes',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 7a2 2 0 0 1 2 2m4 0a6 6 0 0 1-7.743 5.743L11 17H9v2H7v2H4a1 1 0 0 1-1-1v-2.586a1 1 0 0 1 .293-.707l5.964-5.964A6 6 0 1 1 21 9z"
/>
</svg>
),
onClick: handleViewEncryptionKey,
dangerous: false,
},
// {
// id: 'profile',
// title: 'Profile',
// description: 'Update your profile information and preferences',
// icon: (
// <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// strokeWidth={2}
// d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
// />
// </svg>
// ),
// onClick: () => navigateToSettings('profile'),
// dangerous: false,
// },
// {
// id: 'advanced',
// title: 'Advanced',
// description: 'Advanced configuration and developer options',
// icon: (
// <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// strokeWidth={2}
// d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
// />
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// strokeWidth={2}
// d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
// />
// </svg>
// ),
// onClick: () => navigateToSettings('advanced'),
// dangerous: false,
// },
// {
// id: 'encryption',
// title: 'View Encryption Key',
// description: 'Access your encryption key for backup purposes',
// icon: (
// <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// strokeWidth={2}
// d="M15 7a2 2 0 0 1 2 2m4 0a6 6 0 0 1-7.743 5.743L11 17H9v2H7v2H4a1 1 0 0 1-1-1v-2.586a1 1 0 0 1 .293-.707l5.964-5.964A6 6 0 1 1 21 9z"
// />
// </svg>
// ),
// onClick: handleViewEncryptionKey,
// dangerous: false,
// },
{
id: 'team',
title: 'Team',
@@ -140,7 +140,7 @@ const SettingsHome = () => {
},
{
id: 'billing',
title: 'Billing',
title: 'Billing & Usage',
description: 'Manage your subscription and payment methods',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -11,7 +11,7 @@ const PrivacyPanel = () => {
const analyticsEnabled = useAppSelector(state => {
const userId = state.user.user?._id;
if (!userId) return false;
return state.auth.isAnalyticsEnabledByUser[userId] === true;
return state.auth.isAnalyticsEnabledByUser[userId] !== false;
});
const handleToggleAnalytics = () => {
+2 -2
View File
@@ -37,9 +37,9 @@ const Home = () => {
const showUpgradeCTA = currentPlan === 'FREE';
return (
<div className="min-h-screen relative overflow-hidden">
<div className="min-h-full relative">
{/* Content overlay */}
<div className="relative z-10 min-h-screen flex flex-col">
<div className="relative z-10 min-h-full flex flex-col">
{/* Main content */}
<div className="flex-1 flex items-center justify-center p-4">
<div className="max-w-md w-full">
+2 -2
View File
@@ -41,7 +41,7 @@ const Login = () => {
if (consumeError) {
return (
<div className="min-h-screen relative flex items-center justify-center">
<div className="min-h-full relative flex items-center justify-center">
<div className="relative z-10 max-w-md w-full mx-4 text-center">
<div className="glass rounded-3xl p-8 shadow-large animate-fade-up">
<p className="opacity-90 text-coral mb-4">{consumeError}</p>
@@ -55,7 +55,7 @@ const Login = () => {
}
return (
<div className="min-h-screen relative flex items-center justify-center">
<div className="min-h-full relative flex items-center justify-center">
<div className="relative z-10 max-w-md w-full mx-4 text-center">
<div className="glass rounded-3xl p-8 shadow-large animate-fade-up">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto mb-4"></div>
+1 -1
View File
@@ -31,7 +31,7 @@ const Welcome = ({ isWeb }: WelcomeProps) => {
const isPreparing = isLoading && downloadProgress === null;
return (
<div className="min-h-screen relative flex items-center justify-center">
<div className="min-h-full relative flex items-center justify-center">
{/* Main content */}
<div className="relative z-10 max-w-md w-full mx-4 space-y-6">
{/* Welcome card */}
+1 -1
View File
@@ -63,7 +63,7 @@ const Onboarding = () => {
};
return (
<div className="min-h-screen relative flex items-center justify-center">
<div className="min-h-full relative flex items-center justify-center">
<div className="relative z-10 max-w-lg w-full mx-4">
<div className="flex justify-center mb-6">
<LottieAnimation src={stepAnimations[currentStep - 1]} height={120} width={120} />
+61 -63
View File
@@ -1,6 +1,6 @@
import { invoke } from '@tauri-apps/api/core';
import { platform } from '@tauri-apps/plugin-os';
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import {
@@ -19,74 +19,67 @@ const POLL_INTERVAL = 1000;
*/
const ModelProvider = ({ children }: { children: React.ReactNode }) => {
const dispatch = useAppDispatch();
const { loading, downloaded, available, downloadTriggered } = useAppSelector(
state => state.model
);
const pollingRef = useRef(false);
const initDone = useRef(false);
const loading = useAppSelector(state => state.model.loading);
const downloadTriggered = useAppSelector(state => state.model.downloadTriggered);
// Initial status fetch + availability check (runs once)
// Single init effect: fetch status → check platform → auto-download if needed.
// No ref guard — safe to re-run; Rust backend prevents concurrent downloads.
useEffect(() => {
if (initDone.current) return;
initDone.current = true;
let cancelled = false;
const init = async () => {
console.log('[ModelProvider] Initializing...');
// 1. Fetch initial status
let status: ModelStatus;
try {
const status = await invoke<ModelStatus>('model_get_status');
console.log('[ModelProvider] Initial status:', status);
status = await invoke<ModelStatus>('model_get_status');
console.log('[ModelProvider] Initial status:', JSON.stringify(status));
if (cancelled) return;
dispatch(setModelStatus(status));
} catch (err) {
console.log('[ModelProvider] model_get_status failed (non-Tauri?):', err);
return; // Not a Tauri environment, nothing more to do
console.log('[ModelProvider] Not in Tauri environment:', err);
return;
}
// 2. Check availability
try {
const avail = await invoke<boolean>('model_is_available');
console.log('[ModelProvider] Model available:', avail);
if (avail) {
const status = await invoke<ModelStatus>('model_get_status');
dispatch(setModelStatus(status));
}
console.log('[ModelProvider] Available:', avail);
if (!avail || cancelled) return;
status = await invoke<ModelStatus>('model_get_status');
if (cancelled) return;
dispatch(setModelStatus(status));
} catch (err) {
console.log('[ModelProvider] model_is_available failed:', err);
console.log('[ModelProvider] Availability check failed:', err);
return;
}
};
init();
}, [dispatch]);
// Auto-trigger download on desktop when model is available but not downloaded
useEffect(() => {
if (downloadTriggered) {
console.log('[ModelProvider] Auto-download: already triggered, skipping');
return;
}
if (!available) {
console.log('[ModelProvider] Auto-download: not available yet, waiting');
return;
}
if (downloaded) {
console.log('[ModelProvider] Auto-download: already downloaded, skipping');
return;
}
if (loading) {
console.log('[ModelProvider] Auto-download: already loading, skipping');
return;
}
// 3. If already downloaded or already loading, nothing to do
if (status.downloaded) {
console.log('[ModelProvider] Already downloaded, skipping auto-download');
return;
}
if (status.loading) {
console.log('[ModelProvider] Already loading, will poll');
if (!cancelled) dispatch(setModelLoading(true));
return;
}
const tryAutoDownload = async () => {
// 4. Check platform — only auto-download on desktop
try {
const currentPlatform = await platform();
console.log('[ModelProvider] Platform:', currentPlatform);
if (currentPlatform === 'android' || currentPlatform === 'ios') {
console.log('[ModelProvider] Mobile platform, skipping auto-download');
console.log('[ModelProvider] Mobile platform, skipping');
return;
}
} catch (err) {
console.log('[ModelProvider] Platform detection failed (likely web), skipping:', err);
console.log('[ModelProvider] Platform detection failed (web?), skipping:', err);
return;
}
if (cancelled) return;
// 5. Start download
console.log('[ModelProvider] Starting auto-download...');
dispatch(setDownloadTriggered(true));
dispatch(setModelLoading(true));
@@ -94,43 +87,48 @@ const ModelProvider = ({ children }: { children: React.ReactNode }) => {
try {
await invoke('model_start_download');
const status = await invoke<ModelStatus>('model_get_status');
console.log('[ModelProvider] Download started, status:', status);
dispatch(setModelStatus(status));
if (cancelled) return;
const finalStatus = await invoke<ModelStatus>('model_get_status');
console.log('[ModelProvider] Download complete:', JSON.stringify(finalStatus));
if (!cancelled) dispatch(setModelStatus(finalStatus));
} catch (err) {
console.error('[ModelProvider] Auto-download failed:', err);
dispatch(setModelError(err instanceof Error ? err.message : 'Failed to download model'));
console.error('[ModelProvider] Download failed:', err);
if (!cancelled)
dispatch(setModelError(err instanceof Error ? err.message : String(err)));
}
};
tryAutoDownload();
}, [dispatch, downloadTriggered, available, downloaded, loading]);
// Only run if download hasn't been triggered yet (Redux state, survives StrictMode)
if (!downloadTriggered) {
init();
}
return () => {
cancelled = true;
};
}, [dispatch, downloadTriggered]);
// Poll status while loading/downloading
useEffect(() => {
if (!loading) {
pollingRef.current = false;
return;
}
pollingRef.current = true;
console.log('[ModelProvider] Polling started (loading=true)');
if (!loading) return;
console.log('[ModelProvider] Polling started');
const interval = setInterval(async () => {
if (!pollingRef.current) return;
try {
const status = await invoke<ModelStatus>('model_get_status');
dispatch(setModelStatus(status));
if (!status.loading) {
console.log('[ModelProvider] Polling stopped (loading done)', status);
pollingRef.current = false;
console.log('[ModelProvider] Loading finished:', JSON.stringify(status));
}
} catch {
// ignore polling errors
// ignore
}
}, POLL_INTERVAL);
return () => clearInterval(interval);
return () => {
console.log('[ModelProvider] Polling stopped');
clearInterval(interval);
};
}, [dispatch, loading]);
return <>{children}</>;
+1 -1
View File
@@ -30,7 +30,7 @@ export function isAnalyticsEnabled(): boolean {
const state = store.getState();
const userId = state.user?.user?._id;
if (!userId) return false;
return state.auth.isAnalyticsEnabledByUser[userId] === true;
return state.auth.isAnalyticsEnabledByUser[userId] !== false;
}
// ---------------------------------------------------------------------------