diff --git a/.mcp.json b/.mcp.json index 11633fe..9ea125f 100644 --- a/.mcp.json +++ b/.mcp.json @@ -42,6 +42,10 @@ "PERPLEXITY_TIMEOUT_MS": "600000" } }, + "chrome-devtools": { + "command": "npx", + "args": ["-y", "chrome-devtools-mcp@latest"] + }, "browsermcp": { "type": "stdio", "command": "npx", diff --git a/apps/landing/src/app/lab/generate/page.tsx b/apps/landing/src/app/lab/generate/page.tsx new file mode 100644 index 0000000..a9383f9 --- /dev/null +++ b/apps/landing/src/app/lab/generate/page.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { Section } from '@/components/shared/Section'; +import { GenerateFormPlaceholder } from '@/components/lab/GenerateFormPlaceholder'; + +const GeneratePage = () => { + return ( + + + + ); +}; + +export default GeneratePage; diff --git a/apps/landing/src/app/lab/images/page.tsx b/apps/landing/src/app/lab/images/page.tsx new file mode 100644 index 0000000..d270fe7 --- /dev/null +++ b/apps/landing/src/app/lab/images/page.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { Section } from '@/components/shared/Section'; + +const ImagesPage = () => { + return ( + + + + Image Library + + + Browse and manage your generated images + + + + + + 🖼️ + Image Browser + Component will be implemented here + + + + ); +}; + +export default ImagesPage; diff --git a/apps/landing/src/app/lab/layout.tsx b/apps/landing/src/app/lab/layout.tsx new file mode 100644 index 0000000..38c2cff --- /dev/null +++ b/apps/landing/src/app/lab/layout.tsx @@ -0,0 +1,55 @@ +'use client'; + +/** + * Lab Section Layout + * + * Code Style: + * - Use `const` arrow function components (not function declarations) + * - Use `type` instead of `interface` for type definitions + * - Early returns for conditionals + * - No inline comments (JSDoc headers only) + * - Tailwind classes only + * + * Structure: + * - Layout components: src/components/layout/lab/ + * - Feature components: src/components/lab/ + * - Pages: src/app/lab/{section}/page.tsx + * + * Sub-navigation items: + * - /lab/generate - Image generation + * - /lab/images - Image library browser + * - /lab/live - Live generation testing + * - /lab/upload - File upload interface + */ + +import { ReactNode } from 'react'; +import { usePathname } from 'next/navigation'; +import { ApiKeyWidget } from '@/components/shared/ApiKeyWidget/apikey-widget'; +import { ApiKeyProvider } from '@/components/shared/ApiKeyWidget/apikey-context'; +import { PageProvider } from '@/contexts/page-context'; +import { LabLayout } from '@/components/layout/lab/LabLayout'; + +type LabLayoutWrapperProps = { + children: ReactNode; +}; + +const navItems = [ + { label: 'Generate', href: '/lab/generate' }, + { label: 'Images', href: '/lab/images' }, + { label: 'Live', href: '/lab/live' }, + { label: 'Upload', href: '/lab/upload' }, +]; + +const LabLayoutWrapper = ({ children }: LabLayoutWrapperProps) => { + const pathname = usePathname(); + + return ( + + }> + {children} + + + ); +}; + +export default LabLayoutWrapper; diff --git a/apps/landing/src/app/lab/live/page.tsx b/apps/landing/src/app/lab/live/page.tsx new file mode 100644 index 0000000..eb04de6 --- /dev/null +++ b/apps/landing/src/app/lab/live/page.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { Section } from '@/components/shared/Section'; + +const LivePage = () => { + return ( + + + + Live Generation + + + Real-time testing and experimentation workspace + + + + + + ⚡ + Live Testing Interface + Component will be implemented here + + + + ); +}; + +export default LivePage; diff --git a/apps/landing/src/app/lab/page.tsx b/apps/landing/src/app/lab/page.tsx new file mode 100644 index 0000000..ea281ad --- /dev/null +++ b/apps/landing/src/app/lab/page.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; + +const LabPage = () => { + const router = useRouter(); + + useEffect(() => { + router.replace('/lab/generate'); + }, [router]); + + return null; +}; + +export default LabPage; diff --git a/apps/landing/src/app/lab/upload/page.tsx b/apps/landing/src/app/lab/upload/page.tsx new file mode 100644 index 0000000..1fc4c1c --- /dev/null +++ b/apps/landing/src/app/lab/upload/page.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { Section } from '@/components/shared/Section'; + +const UploadPage = () => { + return ( + + + + File Upload + + + Upload and manage reference images for generation + + + + + + 📤 + Upload Interface + Component will be implemented here + + + + ); +}; + +export default UploadPage; diff --git a/apps/landing/src/components/lab/FilterPlaceholder.tsx b/apps/landing/src/components/lab/FilterPlaceholder.tsx new file mode 100644 index 0000000..7932e5d --- /dev/null +++ b/apps/landing/src/components/lab/FilterPlaceholder.tsx @@ -0,0 +1,83 @@ +'use client'; + +/** + * Filter Placeholder Component + * + * Checkbox/radio button group for sidebar filters. + * Supports both single-select (radio) and multi-select (checkbox) modes. + * + * Features: + * - Radio buttons for single selection + * - Checkboxes for multiple selection + * - Option counts (e.g., "All (127)") + * - Accessible keyboard navigation + * - Focus indicators + */ + +import { useState } from 'react'; + +type FilterOption = { + id: string; + label: string; + count?: number; +}; + +type FilterPlaceholderProps = { + options: FilterOption[]; + multiSelect?: boolean; + groupId: string; +}; + +export const FilterPlaceholder = ({ + options, + multiSelect = false, + groupId, +}: FilterPlaceholderProps) => { + const [selected, setSelected] = useState(multiSelect ? [] : [options[0]?.id || '']); + + const handleSelect = (optionId: string) => { + if (multiSelect) { + setSelected((prev) => + prev.includes(optionId) ? prev.filter((id) => id !== optionId) : [...prev, optionId] + ); + } else { + setSelected([optionId]); + } + }; + + const isSelected = (optionId: string) => selected.includes(optionId); + + return ( + + {options.map((option) => { + const checked = isSelected(option.id); + const inputId = `${groupId}-${option.id}`; + + return ( + + handleSelect(option.id)} + className="w-4 h-4 bg-slate-800 border-slate-600 text-purple-600 focus:ring-2 focus:ring-purple-500 focus:ring-offset-0 rounded cursor-pointer" + /> + + {option.label} + + {option.count !== undefined && ( + + {option.count} + + )} + + ); + })} + + ); +}; diff --git a/apps/landing/src/components/lab/GenerateFormPlaceholder.tsx b/apps/landing/src/components/lab/GenerateFormPlaceholder.tsx new file mode 100644 index 0000000..7d3bde7 --- /dev/null +++ b/apps/landing/src/components/lab/GenerateFormPlaceholder.tsx @@ -0,0 +1,211 @@ +'use client'; + +/** + * Generate Form Placeholder Component + * + * Main content placeholder for lab generation interface. + * Matches the /demo/tti page visual style with form card and results area. + * + * Features: + * - Header with title and description + * - Prompt textarea (similar to TTI page) + * - Options row with selects and buttons + * - Submit button with gradient styling + * - Results area placeholder + * - Keyboard shortcuts (Ctrl+Enter) + */ + +import { useState, useRef, KeyboardEvent } from 'react'; + +export const GenerateFormPlaceholder = () => { + const [prompt, setPrompt] = useState(''); + const [aspectRatio, setAspectRatio] = useState('1:1'); + const [template, setTemplate] = useState('photorealistic'); + const [generating, setGenerating] = useState(false); + const textareaRef = useRef(null); + + const handleGenerate = () => { + if (!prompt.trim()) return; + setGenerating(true); + setTimeout(() => setGenerating(false), 2000); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.ctrlKey && e.key === 'Enter') { + e.preventDefault(); + handleGenerate(); + } + }; + + return ( + + {/* Page Header */} + + + Generate Workbench + + + Create and experiment with AI-generated images + + + + {/* Info Banner (Placeholder) */} + + + + Lab Environment + + This is an experimental interface for testing generation features + + + + + Active + + + + + {/* Generation Form Card */} + + {/* Prompt Textarea */} + + + Your Prompt + + setPrompt(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Describe the image you want to generate..." + disabled={generating} + rows={5} + className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed resize-none" + aria-label="Image generation prompt" + /> + + Tip: Be specific and descriptive for better results + + + + {/* Options Row */} + + {/* Aspect Ratio */} + + + Aspect Ratio + + setAspectRatio(e.target.value)} + disabled={generating} + className="w-full px-3 py-2 text-sm bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed" + > + Square (1:1) + Portrait (3:4) + Landscape (4:3) + Vertical (9:16) + Widescreen (16:9) + Ultrawide (21:9) + + + + {/* Template */} + + + Style Template + + setTemplate(e.target.value)} + disabled={generating} + className="w-full px-3 py-2 text-sm bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed" + > + Photorealistic + Illustration + Minimalist + Sticker + Product + Comic + General + + + + {/* Advanced Options Button */} + + + Advanced Options + + + ⚙️ + Configure + + + + + {/* Submit Button Row */} + + + {generating ? ( + + + Generating... + + ) : ( + 'Press Ctrl+Enter to submit' + )} + + + {generating ? 'Generating...' : 'Generate Images'} + + + + + {/* Results Area Placeholder */} + + + Results + + Clear All + + + + {/* Empty State */} + + 🎨 + No results yet + + Generate your first image to see results here + + + + {/* Result Cards Placeholder (would appear after generation) */} + {generating && ( + + + + + + + )} + + + ); +}; diff --git a/apps/landing/src/components/layout/lab/LabLayout.tsx b/apps/landing/src/components/layout/lab/LabLayout.tsx new file mode 100644 index 0000000..738df36 --- /dev/null +++ b/apps/landing/src/components/layout/lab/LabLayout.tsx @@ -0,0 +1,39 @@ +'use client'; + +/** + * Lab Layout Component + * + * Main layout wrapper for the Lab section combining sidebar and content. + * Uses ThreeColumnLayout for consistent column structure across the app. + * + * Structure: + * - Left: LabSidebar (w-64, hidden lg:block) + * - Center: Page content (flex-1) + * - Right: Reserved for future use (TOC, preview panels, etc.) + * + * This layout is rendered inside PageProvider context which provides: + * - SubsectionNav at the top + * - ApiKeyWidget in the right slot + * - AnimatedBackground + */ + +import { ReactNode } from 'react'; +import { ThreeColumnLayout } from '@/components/layout/ThreeColumnLayout'; +import { LabSidebar } from './LabSidebar'; + +type LabLayoutProps = { + children: ReactNode; +}; + +export const LabLayout = ({ children }: LabLayoutProps) => { + return ( + + + + } + center={children} + /> + ); +}; diff --git a/apps/landing/src/components/layout/lab/LabSidebar.tsx b/apps/landing/src/components/layout/lab/LabSidebar.tsx new file mode 100644 index 0000000..9467eba --- /dev/null +++ b/apps/landing/src/components/layout/lab/LabSidebar.tsx @@ -0,0 +1,135 @@ +'use client'; + +/** + * Lab Sidebar - Filter Panel + * + * Narrow left sidebar for filtering lab content. + * Matches DocsSidebar visual style with collapsible filter sections. + * + * Features: + * - Multiple filter groups (Status, Date Range, Source Type) + * - Collapsible sections + * - Checkbox/radio button controls + * - Clean, minimal design + */ + +import { useState } from 'react'; +import { FilterPlaceholder } from '@/components/lab/FilterPlaceholder'; + +type FilterGroupProps = { + id: string; + label: string; + icon: string; + options: Array<{ id: string; label: string; count?: number }>; + multiSelect?: boolean; +}; + +const filterGroups: FilterGroupProps[] = [ + { + id: 'status', + label: 'Status', + icon: '📊', + options: [ + { id: 'all', label: 'All', count: 127 }, + { id: 'completed', label: 'Completed', count: 98 }, + { id: 'pending', label: 'Pending', count: 23 }, + { id: 'failed', label: 'Failed', count: 6 }, + ], + multiSelect: false, + }, + { + id: 'date', + label: 'Date Range', + icon: '📅', + options: [ + { id: 'today', label: 'Today', count: 12 }, + { id: 'week', label: 'Last 7 days', count: 45 }, + { id: 'month', label: 'Last 30 days', count: 89 }, + { id: 'all-time', label: 'All time', count: 127 }, + ], + multiSelect: false, + }, + { + id: 'source', + label: 'Source Type', + icon: '🎨', + options: [ + { id: 'text', label: 'Text-to-Image', count: 78 }, + { id: 'upload', label: 'Uploads', count: 34 }, + { id: 'reference', label: 'Reference Images', count: 15 }, + ], + multiSelect: true, + }, +]; + +export const LabSidebar = () => { + const [expandedSections, setExpandedSections] = useState(['status', 'date', 'source']); + + const toggleSection = (id: string) => { + setExpandedSections((prev) => + prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id] + ); + }; + + const isExpanded = (id: string) => expandedSections.includes(id); + + return ( + + ); +};
+ Browse and manage your generated images +
Component will be implemented here
+ Real-time testing and experimentation workspace +
+ Upload and manage reference images for generation +
+ Create and experiment with AI-generated images +
+ This is an experimental interface for testing generation features +
+ Tip: Be specific and descriptive for better results +
+ Generate your first image to see results here +