Fixed initial load being slow
This commit is contained in:
@@ -54,7 +54,14 @@
|
||||
"Bash(npx eslint:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"mcp__plugin_playwright_playwright__browser_hover",
|
||||
"mcp__plugin_playwright_playwright__browser_run_code"
|
||||
"mcp__plugin_playwright_playwright__browser_run_code",
|
||||
"WebFetch(domain:pagespeed.web.dev)",
|
||||
"WebFetch(domain:www.googleapis.com)",
|
||||
"Bash(find:*)",
|
||||
"Bash(du -sh:*)",
|
||||
"Bash(sudo apt-get install:*)",
|
||||
"Bash(pdftotext:*)",
|
||||
"Bash(pdftoppm:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -4,11 +4,13 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Andy Charlwood — Deputy Head of Population Health & Data Analysis. Interactive CV and portfolio showcasing pharmacist expertise, data analytics, and population health management.">
|
||||
<title>CVMIS: CHARLWOOD, A.</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap"></noscript>
|
||||
<link rel="preload" href="/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<script defer src="https://analytics.charlwood.xyz/script.js" data-website-id="075e79d5-433a-4192-91c0-0b5b9c4334ab"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Binary file not shown.
+3
-4
@@ -56,10 +56,9 @@ function App() {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
initModel()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (phase === 'login' || phase === 'pmr') {
|
||||
initModel()
|
||||
}
|
||||
if (phase === 'pmr') {
|
||||
sessionStorage.setItem('portfolio-visited', String(Date.now()))
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react'
|
||||
import { useState, useEffect, useCallback, useRef, useMemo, lazy, Suspense } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import Sidebar from './Sidebar'
|
||||
import { MobileBottomNav } from './MobileBottomNav'
|
||||
import { CommandPalette } from './CommandPalette'
|
||||
import { DetailPanel } from './DetailPanel'
|
||||
import { PatientSummaryTile } from './tiles/PatientSummaryTile'
|
||||
import { ParentSection } from './ParentSection'
|
||||
import CareerConstellation from './constellation/CareerConstellation'
|
||||
import { TimelineInterventionsSubsection } from './TimelineInterventionsSubsection'
|
||||
import { RepeatMedicationsSubsection } from './RepeatMedicationsSubsection'
|
||||
import { LastConsultationCard } from './LastConsultationCard'
|
||||
import { ChatWidget } from './ChatWidget'
|
||||
import { MobileOverviewHeader } from './MobileOverviewHeader'
|
||||
|
||||
const CommandPalette = lazy(() => import('./CommandPalette').then(m => ({ default: m.CommandPalette })))
|
||||
const DetailPanel = lazy(() => import('./DetailPanel').then(m => ({ default: m.DetailPanel })))
|
||||
const CareerConstellation = lazy(() => import('./constellation/CareerConstellation'))
|
||||
const RepeatMedicationsSubsection = lazy(() => import('./RepeatMedicationsSubsection').then(m => ({ default: m.RepeatMedicationsSubsection })))
|
||||
const ChatWidget = lazy(() => import('./ChatWidget').then(m => ({ default: m.ChatWidget })))
|
||||
import { useActiveSection } from '@/hooks/useActiveSection'
|
||||
import { useIsMobileNav } from '@/hooks/useIsMobileNav'
|
||||
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||
@@ -329,22 +330,26 @@ export function DashboardLayout() {
|
||||
</div>
|
||||
</div>
|
||||
<div ref={constellationWrapperRef} className="pathway-graph-sticky">
|
||||
<CareerConstellation
|
||||
onRoleClick={handleRoleClick}
|
||||
onSkillClick={handleSkillClick}
|
||||
onNodeHover={handleNodeHover}
|
||||
highlightedNodeId={highlightedNodeId}
|
||||
containerHeight={chronologyHeight}
|
||||
animationReady={constellationReady}
|
||||
globalFocusActive={globalFocusId !== null}
|
||||
/>
|
||||
<Suspense fallback={null}>
|
||||
<CareerConstellation
|
||||
onRoleClick={handleRoleClick}
|
||||
onSkillClick={handleSkillClick}
|
||||
onNodeHover={handleNodeHover}
|
||||
highlightedNodeId={highlightedNodeId}
|
||||
containerHeight={chronologyHeight}
|
||||
animationReady={constellationReady}
|
||||
globalFocusActive={globalFocusId !== null}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div data-tile-id="section-skills" style={{ marginTop: '22px' }}>
|
||||
<RepeatMedicationsSubsection onNodeHighlight={handleNodeHighlight} focusRelatedIds={focusRelatedIds} />
|
||||
<Suspense fallback={null}>
|
||||
<RepeatMedicationsSubsection onNodeHighlight={handleNodeHighlight} focusRelatedIds={focusRelatedIds} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</ParentSection>
|
||||
</div>
|
||||
@@ -352,17 +357,23 @@ export function DashboardLayout() {
|
||||
</div>
|
||||
|
||||
{/* Command palette overlay */}
|
||||
<CommandPalette
|
||||
isOpen={commandPaletteOpen}
|
||||
onClose={handlePaletteClose}
|
||||
onAction={handlePaletteAction}
|
||||
/>
|
||||
<Suspense fallback={null}>
|
||||
<CommandPalette
|
||||
isOpen={commandPaletteOpen}
|
||||
onClose={handlePaletteClose}
|
||||
onAction={handlePaletteAction}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Detail panel */}
|
||||
<DetailPanel />
|
||||
<Suspense fallback={null}>
|
||||
<DetailPanel />
|
||||
</Suspense>
|
||||
|
||||
{/* Floating chat widget */}
|
||||
<ChatWidget onAction={handlePaletteAction} />
|
||||
<Suspense fallback={null}>
|
||||
<ChatWidget onAction={handlePaletteAction} />
|
||||
</Suspense>
|
||||
|
||||
{/* Mobile bottom navigation */}
|
||||
<MobileBottomNav
|
||||
|
||||
@@ -3,15 +3,6 @@
|
||||
@tailwind utilities;
|
||||
|
||||
/* Premium UI fonts — Elvaro Grotesque (primary) */
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Light.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Regular.woff2') format('woff2'),
|
||||
@@ -48,34 +39,7 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-ExtraBold.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-ExtraBold.woff') format('woff');
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Black.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Black.woff') format('woff');
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Monospace — Interval Mono */
|
||||
@font-face {
|
||||
font-family: 'Interval Mono';
|
||||
src: url('/Fonts/IntervalMono/WOFF/TBJInterval-Light.woff2') format('woff2'),
|
||||
url('/Fonts/IntervalMono/WOFF/TBJInterval-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Interval Mono';
|
||||
src: url('/Fonts/IntervalMono/WOFF/TBJInterval-Regular.woff2') format('woff2'),
|
||||
@@ -85,15 +49,6 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Interval Mono';
|
||||
src: url('/Fonts/IntervalMono/WOFF/TBJInterval-Bold.woff2') format('woff2'),
|
||||
url('/Fonts/IntervalMono/WOFF/TBJInterval-Bold.woff') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Premium UI fonts — Blumir (alternative) */
|
||||
@font-face {
|
||||
font-family: 'Blumir';
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { env, pipeline, type FeatureExtractionPipeline } from '@xenova/transformers'
|
||||
|
||||
// Serve model files from /models/ (Vite serves public/ at root)
|
||||
env.localModelPath = '/models/'
|
||||
env.allowRemoteModels = false
|
||||
env.useBrowserCache = false
|
||||
|
||||
let extractor: FeatureExtractionPipeline | null = null
|
||||
let extractor: import('@xenova/transformers').FeatureExtractionPipeline | null = null
|
||||
let loading = false
|
||||
|
||||
export async function initModel(): Promise<void> {
|
||||
if (extractor || loading) return
|
||||
loading = true
|
||||
try {
|
||||
extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2') as FeatureExtractionPipeline
|
||||
const { env, pipeline } = await import('@xenova/transformers')
|
||||
env.localModelPath = '/models/'
|
||||
env.allowRemoteModels = false
|
||||
env.useBrowserCache = false
|
||||
extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2') as import('@xenova/transformers').FeatureExtractionPipeline
|
||||
} catch {
|
||||
// Silently swallow — model unavailable, semantic search won't activate
|
||||
} finally {
|
||||
|
||||
@@ -9,6 +9,20 @@ export default defineConfig({
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'vendor-react': ['react', 'react-dom'],
|
||||
'vendor-d3': ['d3'],
|
||||
'vendor-motion': ['framer-motion'],
|
||||
'vendor-search': ['fuse.js'],
|
||||
'vendor-markdown': ['react-markdown'],
|
||||
'vendor-carousel': ['embla-carousel-react', 'embla-carousel-autoplay'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': 'http://localhost:3000',
|
||||
|
||||
Reference in New Issue
Block a user