diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 74bd7de..668de0a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,18 +1,21 @@ -import { useState } from "react"; -import { ConfigurationView } from "./views/ConfigurationView"; -import { RepositoryView } from "./views/RepositoryView"; -import { EnhancedIrView } from "./views/EnhancedIrView"; -import { PlannerView } from "./views/PlannerView"; -import { IdeView } from "./views/IdeView"; -import { PromptWorkbenchView } from "./views/PromptWorkbenchView"; +import { useState, lazy, Suspense } from "react"; import { Button } from "./components/ui/button"; import { ModeToggle } from "./components/mode-toggle"; import { Separator } from "./components/ui/separator"; +import { SkipLink } from "./components/skip-link"; import { useAuth } from "./lib/auth"; import { SignInView } from "./views/SignInView"; import { AuthCallbackView } from "./views/AuthCallbackView"; import "./styles.css"; +// Lazy load views for code splitting +const ConfigurationView = lazy(() => import("./views/ConfigurationView").then(m => ({ default: m.ConfigurationView }))); +const RepositoryView = lazy(() => import("./views/RepositoryView").then(m => ({ default: m.RepositoryView }))); +const EnhancedIrView = lazy(() => import("./views/EnhancedIrView").then(m => ({ default: m.EnhancedIrView }))); +const PlannerView = lazy(() => import("./views/PlannerView").then(m => ({ default: m.PlannerView }))); +const IdeView = lazy(() => import("./views/IdeView").then(m => ({ default: m.IdeView }))); +const PromptWorkbenchView = lazy(() => import("./views/PromptWorkbenchView").then(m => ({ default: m.PromptWorkbenchView }))); + type Section = "configuration" | "repository" | "prompt" | "ir" | "planner" | "ide"; export default function App() { @@ -39,17 +42,20 @@ export default function App() { const userLabel = state.user?.name ?? state.user?.email ?? state.user?.id; return ( -
- -
- {section === "configuration" && } - {section === "repository" && } - {section === "prompt" && } - {section === "ir" && } - {section === "planner" && } - {section === "ide" && } -
-
+ +
+ +

Loading...

+ + }> + {section === "configuration" && } + {section === "repository" && } + {section === "prompt" && } + {section === "ir" && } + {section === "planner" && } + {section === "ide" && } +
+
+ + ); } diff --git a/frontend/src/components/skip-link.tsx b/frontend/src/components/skip-link.tsx new file mode 100644 index 0000000..2af12f7 --- /dev/null +++ b/frontend/src/components/skip-link.tsx @@ -0,0 +1,40 @@ +import { cn } from "@/lib/utils"; + +interface SkipLinkProps { + href: string; + children: React.ReactNode; + className?: string; +} + +/** + * SkipLink - Accessibility component for keyboard navigation + * + * Allows keyboard users to skip directly to main content areas. + * Hidden by default, becomes visible when focused via Tab key. + * + * @example + * Skip to main content + * Skip to navigation + */ +export function SkipLink({ href, children, className }: SkipLinkProps) { + return ( + + {children} + + ); +} diff --git a/frontend/src/views/ConfigurationView.tsx b/frontend/src/views/ConfigurationView.tsx index 333f216..4cb56a7 100644 --- a/frontend/src/views/ConfigurationView.tsx +++ b/frontend/src/views/ConfigurationView.tsx @@ -1,7 +1,12 @@ import { useMutation } from "@tanstack/react-query"; import { useCallback, useState } from "react"; -import { Button } from "../components/ui/button"; -import { Card } from "../components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { CheckCircle2 } from "lucide-react"; import { ProviderSelector } from "../components/ProviderSelector"; import { Provider } from "../types/providers"; import { api } from "../lib/api"; @@ -29,38 +34,82 @@ export function ConfigurationView() { }, []); return ( - - - - - {mutation.isSuccess &&

Configuration saved.

} -
-

AI Providers

- - {primaryProvider &&

Primary provider: {primaryProvider.toUpperCase()}

} -
-
+
+
+

Configuration

+

+ Configure model endpoints and AI provider settings +

+
+ + + + Model Configuration + + +
+ + setEndpoint(event.target.value)} + placeholder="http://localhost:8001" + /> +

+ URL for the local or remote model API endpoint +

+
+ +
+ + setTemperature(Number(event.target.value))} + /> +

+ Controls randomness: 0 is deterministic, higher values increase creativity +

+
+
+ + + {mutation.isSuccess && ( + + + Configuration saved successfully + + )} + +
+ + + + + + AI Providers + + + + {primaryProvider && ( + + + Primary provider: {primaryProvider.toUpperCase()} + + + )} + + +
); } - -const inputStyle: React.CSSProperties = { - padding: "0.75rem", - borderRadius: "0.75rem", - border: "1px solid #334155", - background: "#0f172a", - color: "#e2e8f0", -}; diff --git a/frontend/src/views/SignInView.tsx b/frontend/src/views/SignInView.tsx index 2412086..f9e7273 100644 --- a/frontend/src/views/SignInView.tsx +++ b/frontend/src/views/SignInView.tsx @@ -1,4 +1,7 @@ -import { Button } from "../components/ui/button"; +import { AlertCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { useAuth, OAuthProvider } from "../lib/auth"; const PROVIDER_LABELS: Record = { @@ -11,34 +14,31 @@ export function SignInView() { const errorMessage = state.error; return ( -
-
-

Welcome to lift-sys

-

- Sign in to connect your AI providers, manage repositories, and orchestrate code - generation workflows. -

-
-
- {(Object.keys(PROVIDER_LABELS) as OAuthProvider[]).map((provider) => ( - - ))} -
- {errorMessage && ( -

- {errorMessage} -

- )} +
+ + + Welcome to lift-sys + + Sign in to connect your AI providers, manage repositories, and orchestrate code + generation workflows. + + + +
+ {(Object.keys(PROVIDER_LABELS) as OAuthProvider[]).map((provider) => ( + + ))} +
+ {errorMessage && ( + + + {errorMessage} + + )} +
+
); } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 015973f..9f99d8c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -9,6 +9,47 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, + build: { + rollupOptions: { + output: { + manualChunks: { + // React core + "vendor-react": ["react", "react-dom", "react/jsx-runtime"], + + // React Query + "vendor-query": ["@tanstack/react-query"], + + // Radix UI primitives + "vendor-radix": [ + "@radix-ui/react-slot", + "@radix-ui/react-dialog", + "@radix-ui/react-dropdown-menu", + "@radix-ui/react-popover", + "@radix-ui/react-tooltip", + "@radix-ui/react-tabs", + "@radix-ui/react-accordion", + "@radix-ui/react-separator", + "@radix-ui/react-label", + "@radix-ui/react-select", + "@radix-ui/react-checkbox", + "@radix-ui/react-radio-group", + "@radix-ui/react-switch", + "@radix-ui/react-slider", + "@radix-ui/react-avatar", + "@radix-ui/react-progress", + ], + + // Icons + "vendor-icons": ["lucide-react"], + + // Utilities + "vendor-utils": ["clsx", "tailwind-merge", "class-variance-authority"], + }, + }, + }, + // Increase chunk size warning limit since we're code splitting + chunkSizeWarningLimit: 600, + }, server: { port: 5173, proxy: {