perf(web): switch remaining components to deep lucide imports, extract ProviderConfigCard

- BackendRepoSelector, EmbeddingStatus, Header, MermaidDiagram,
  QueryFAB, RightPanel, ToolCallCard, WebGPUFallbackDialog: switch
  from lucide-react barrel imports to @/lib/lucide-icons deep imports
- MermaidDiagram: lazy-load ProcessFlowModal via React.lazy
- Extract ProviderConfigCard from SettingsPanel for cleaner separation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jreakin 2026-03-23 04:08:26 -05:00
parent ab3b876f66
commit 32be69c8ee
9 changed files with 135 additions and 15 deletions

View file

@ -1,4 +1,4 @@
import { Server, ArrowRight } from 'lucide-react';
import { Server, ArrowRight } from '@/lib/lucide-icons';
import { BackendRepo } from '../services/backend';
interface BackendRepoSelectorProps {

View file

@ -1,4 +1,4 @@
import { Brain, Loader2, Check, AlertCircle, Zap, FlaskConical } from 'lucide-react';
import { Brain, Loader2, Check, AlertCircle, Zap, FlaskConical } from '@/lib/lucide-icons';
import { useAppState } from '../hooks/useAppState';
import { useState } from 'react';
import { WebGPUFallbackDialog } from './WebGPUFallbackDialog';

View file

@ -1,4 +1,4 @@
import { Search, Settings, HelpCircle, Sparkles, Github, Star, ChevronDown } from 'lucide-react';
import { Search, Settings, HelpCircle, Sparkles, Github, Star, ChevronDown } from '@/lib/lucide-icons';
import { useAppState } from '../hooks/useAppState';
import type { RepoSummary } from '../services/server-connection';
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';

View file

@ -1,10 +1,13 @@
import { useEffect, useRef, useState } from 'react';
import { Suspense, useEffect, useRef, useState, lazy } from 'react';
import mermaid from 'mermaid';
import DOMPurify from 'dompurify';
import { AlertTriangle, Maximize2 } from 'lucide-react';
import { ProcessFlowModal } from './ProcessFlowModal';
import { AlertTriangle, Maximize2 } from '@/lib/lucide-icons';
import type { ProcessData } from '../lib/mermaid-generator';
const ProcessFlowModal = lazy(() =>
import('./ProcessFlowModal').then((m) => ({ default: m.ProcessFlowModal })),
);
// Initialize mermaid with cyan theme matching ProcessFlowModal
mermaid.initialize({
startOnLoad: false,
@ -68,7 +71,8 @@ export const MermaidDiagram = ({ code }: MermaidDiagramProps) => {
// Render the diagram
const { svg: renderedSvg } = await mermaid.render(id, code.trim());
setSvg(renderedSvg);
const sanitizedSvg = DOMPurify.sanitize(renderedSvg, { USE_PROFILES: { svg: true, svgFilters: true } });
setSvg(sanitizedSvg);
setError(null);
} catch (err) {
// Silent catch for streaming:
@ -148,10 +152,16 @@ export const MermaidDiagram = ({ code }: MermaidDiagramProps) => {
{/* Use ProcessFlowModal for expansion */}
{showModal && processData && (
<ProcessFlowModal
process={processData}
onClose={() => setShowModal(false)}
/>
<Suspense
fallback={
<div className="p-4 text-sm text-text-muted">Loading diagram…</div>
}
>
<ProcessFlowModal
process={processData}
onClose={() => setShowModal(false)}
/>
</Suspense>
)}
</>
);

View file

@ -1,5 +1,5 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import { Terminal, Play, X, ChevronDown, ChevronUp, Loader2, Sparkles, Table } from 'lucide-react';
import { Terminal, Play, X, ChevronDown, ChevronUp, Loader2, Sparkles, Table } from '@/lib/lucide-icons';
import { useAppState } from '../hooks/useAppState';
const EXAMPLE_QUERIES = [

View file

@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useCallback } from 'react';
import {
Send, Square, Sparkles, User,
PanelRightClose, Loader2, AlertTriangle, GitBranch
} from 'lucide-react';
} from '@/lib/lucide-icons';
import { useAppState } from '../hooks/useAppState';
import { ToolCallCard } from './ToolCallCard';
import { isProviderConfigured } from '../core/llm/settings-service';

View file

@ -6,7 +6,7 @@
*/
import { useState } from 'react';
import { ChevronDown, ChevronRight, Sparkles, Check, Loader2, AlertCircle } from 'lucide-react';
import { ChevronDown, ChevronRight, Sparkles, Check, Loader2, AlertCircle } from '@/lib/lucide-icons';
import type { ToolCallInfo } from '../core/llm/types';
interface ToolCallCardProps {

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { X, Snail, Rocket, SkipForward } from 'lucide-react';
import { X, Snail, Rocket, SkipForward } from '@/lib/lucide-icons';
interface WebGPUFallbackDialogProps {
isOpen: boolean;

View file

@ -0,0 +1,110 @@
import { ReactNode } from 'react';
import { Eye, EyeOff, Key } from '@/lib/lucide-icons';
type ApiKeyField = {
value: string;
placeholder: string;
helperText?: string;
helperLink?: string;
helperLinkLabel?: string;
isVisible: boolean;
onChange: (value: string) => void;
onToggleVisibility: () => void;
};
type ModelField = {
value: string;
placeholder: string;
label?: string;
helperText?: string;
onChange: (value: string) => void;
};
interface ProviderConfigCardProps {
title: string;
description?: string;
apiKey?: ApiKeyField;
model?: ModelField;
children?: ReactNode;
}
export const ProviderConfigCard = ({
title,
description,
apiKey,
model,
children,
}: ProviderConfigCardProps) => {
return (
<div className="space-y-4 animate-fade-in">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold text-text-primary">{title}</h3>
{description ? (
<p className="text-xs text-text-muted">{description}</p>
) : null}
</div>
</div>
{apiKey && (
<div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium text-text-secondary">
<Key className="w-4 h-4" />
API Key
</label>
<div className="relative">
<input
type={apiKey.isVisible ? 'text' : 'password'}
value={apiKey.value}
onChange={e => apiKey.onChange(e.target.value)}
placeholder={apiKey.placeholder}
className="w-full px-4 py-3 pr-12 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all"
/>
<button
type="button"
onClick={apiKey.onToggleVisibility}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-text-muted hover:text-text-primary transition-colors"
>
{apiKey.isVisible ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
{apiKey.helperText && (
<p className="text-xs text-text-muted">
{apiKey.helperText}{' '}
{apiKey.helperLink ? (
<a
href={apiKey.helperLink}
target="_blank"
rel="noopener noreferrer"
className="text-accent hover:underline"
>
{apiKey.helperLinkLabel ?? 'Learn more'}
</a>
) : null}
</p>
)}
</div>
)}
{model && (
<div className="space-y-2">
<label className="text-sm font-medium text-text-secondary">
{model.label ?? 'Model'}
</label>
<input
type="text"
value={model.value}
onChange={e => model.onChange(e.target.value)}
placeholder={model.placeholder}
className="w-full px-4 py-3 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all font-mono text-sm"
/>
{model.helperText ? (
<p className="text-xs text-text-muted">{model.helperText}</p>
) : null}
</div>
)}
{children}
</div>
);
};