17 KiB
React Frontend Implementation Plan for Sim-Search
Overview
This document outlines the plan for implementing a React frontend for the sim-search project, replacing the current Gradio interface with a modern, responsive, and feature-rich user interface. The frontend will communicate with the new FastAPI backend to provide a seamless user experience.
Architecture
Core Components
-
Next.js Framework
- Server-side rendering for improved SEO
- API routes for backend proxying if needed
- Static site generation for performance
-
Component Library
- Modular React components
- Reusable UI elements
- Styling with Tailwind CSS
-
State Management
- React Query for server state
- Context API for application state
- Form state management
-
Authentication
- JWT token management
- Protected routes
- User profile management
Directory Structure
sim-search-ui/
├── src/
│ ├── components/
│ │ ├── layout/
│ │ │ ├── Header.jsx # Application header
│ │ │ ├── Sidebar.jsx # Sidebar menu
│ │ │ └── Layout.jsx # Main layout wrapper
│ │ ├── search/
│ │ │ ├── SearchForm.jsx # Search input form
│ │ │ ├── SearchResults.jsx # Results display
│ │ │ ├── ResultItem.jsx # Individual result
│ │ │ └── EngineSelector.jsx # Search engine selector
│ │ ├── report/
│ │ │ ├── ReportGenerator.jsx # Report generation form
│ │ │ ├── ReportViewer.jsx # Report display
│ │ │ ├── ReportsList.jsx # Reports list/management
│ │ │ └── ReportOptions.jsx # Report generation options
│ │ ├── common/
│ │ │ ├── Button.jsx # Reusable button component
│ │ │ ├── Card.jsx # Card container component
│ │ │ ├── Loading.jsx # Loading indicator
│ │ │ └── Modal.jsx # Modal dialog
│ │ └── auth/
│ │ ├── LoginForm.jsx # User login form
│ │ └── RegisterForm.jsx # User registration form
│ ├── hooks/
│ │ ├── useAuth.js # Authentication hook
│ │ ├── useSearch.js # Search execution hook
│ │ └── useReport.js # Report management hook
│ ├── context/
│ │ ├── AuthContext.jsx # Authentication context
│ │ └── SearchContext.jsx # Search state context
│ ├── services/
│ │ ├── api.js # API client service
│ │ ├── auth.js # Authentication service
│ │ ├── search.js # Search service
│ │ └── report.js # Report service
│ ├── utils/
│ │ ├── formatting.js # Text/data formatting utilities
│ │ └── validation.js # Form validation utilities
│ ├── styles/
│ │ ├── globals.css # Global styles
│ │ └── theme.js # Theme configuration
│ └── pages/
│ ├── _app.jsx # App component
│ ├── index.jsx # Home page
│ ├── search.jsx # Search page
│ ├── reports/
│ │ ├── index.jsx # Reports list page
│ │ ├── [id].jsx # Individual report page
│ │ └── new.jsx # New report page
│ └── auth/
│ ├── login.jsx # Login page
│ └── register.jsx # Registration page
├── public/
│ ├── logo.svg # Application logo
│ └── favicon.ico # Favicon
├── tailwind.config.js # Tailwind configuration
├── next.config.js # Next.js configuration
└── package.json # Dependencies
Key Pages and Features
Home Page
- Overview of the system
- Quick access to search and reports
- Feature highlights and documentation
Search Page
- Comprehensive search form
- Multiple search engine selection
- Advanced search options
- Results display with filtering and sorting
- Options to generate reports from results
Report Generation Page
- Detail level selection
- Query type selection
- Model selection
- Advanced options
- Progress tracking
Reports Management Page
- List of generated reports
- Filtering and sorting options
- Download in different formats
- Delete and manage reports
Authentication Pages
- Login page
- Registration page
- User profile management
Component Design
Search Components
SearchForm Component
const SearchForm = ({ onSearchComplete }) => {
const [query, setQuery] = useState('');
const [selectedEngines, setSelectedEngines] = useState([]);
const [numResults, setNumResults] = useState(10);
const [useReranker, setUseReranker] = useState(true);
const { engines, loading, error, loadEngines, search } = useSearch();
// Load available search engines on component mount
useEffect(() => {
loadEngines();
}, []);
// Handle search submission
const handleSubmit = async (e) => {
e.preventDefault();
const searchParams = {
query: query.trim(),
search_engines: selectedEngines.length > 0 ? selectedEngines : undefined,
num_results: numResults,
use_reranker: useReranker,
};
const results = await search(searchParams);
if (results && onSearchComplete) {
onSearchComplete(results);
}
};
return (
// Form UI with input fields, engine selection, and options
);
};
SearchResults Component
const SearchResults = ({ results, query, onGenerateReport }) => {
const [selectedResults, setSelectedResults] = useState([]);
const [sortBy, setSortBy] = useState('relevance');
// Toggle a result's selection
const toggleResultSelection = (resultId) => {
setSelectedResults(prev => (
prev.includes(resultId)
? prev.filter(id => id !== resultId)
: [...prev, resultId]
));
};
// Handle generate report button click
const handleGenerateReport = () => {
// Filter results to only include selected ones if any are selected
const resultsToUse = selectedResults.length > 0
? results.filter((result, index) => selectedResults.includes(index))
: results;
if (onGenerateReport) {
onGenerateReport(resultsToUse, query);
}
};
return (
// Results UI with sorting, filtering, and item selection
);
};
Report Components
ReportGenerator Component
const ReportGenerator = ({ query, searchResults, searchId }) => {
const [detailLevel, setDetailLevel] = useState('standard');
const [queryType, setQueryType] = useState('auto-detect');
const [customModel, setCustomModel] = useState('');
const [initialResults, setInitialResults] = useState(10);
const [finalResults, setFinalResults] = useState(7);
const { loading, error, createReport } = useReport();
// Generate the report
const handleGenerateReport = async () => {
const reportParams = {
query,
search_id: searchId,
search_results: !searchId ? searchResults : undefined,
detail_level: detailLevel,
query_type: queryType,
custom_model: customModel || undefined,
initial_results: initialResults,
final_results: finalResults
};
await createReport(reportParams);
};
return (
// Report generation form with options
);
};
ReportViewer Component
const ReportViewer = ({ report, onDownload }) => {
const [selectedFormat, setSelectedFormat] = useState('markdown');
const { download, loading } = useReport();
const handleDownload = async () => {
if (onDownload) {
onDownload(report.id, selectedFormat);
} else {
await download(report.id, selectedFormat);
}
};
return (
// Report content display with markdown rendering and download options
);
};
API Integration Services
API Client Service
import axios from 'axios';
// Create an axios instance with default config
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
headers: {
'Content-Type': 'application/json',
},
});
// Add a request interceptor to include auth token in requests
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Add a response interceptor to handle common errors
api.interceptors.response.use(
(response) => response,
(error) => {
// Handle 401 Unauthorized - redirect to login
if (error.response && error.response.status === 401) {
localStorage.removeItem('token');
window.location.href = '/auth/login';
}
return Promise.reject(error);
}
);
export default api;
Search Service
import api from './api';
export const executeSearch = async (searchParams) => {
try {
const response = await api.post('/api/search/execute', searchParams);
return { success: true, data: response.data };
} catch (error) {
return {
success: false,
error: error.response?.data?.detail || 'Failed to execute search'
};
}
};
export const getAvailableEngines = async () => {
try {
const response = await api.get('/api/search/engines');
return { success: true, data: response.data };
} catch (error) {
return {
success: false,
error: error.response?.data?.detail || 'Failed to get search engines'
};
}
};
Report Service
import api from './api';
export const generateReport = async (reportParams) => {
try {
const response = await api.post('/api/report/generate', reportParams);
return { success: true, data: response.data };
} catch (error) {
return {
success: false,
error: error.response?.data?.detail || 'Failed to generate report'
};
}
};
export const getReportsList = async (skip = 0, limit = 100) => {
try {
const response = await api.get(`/api/report/list?skip=${skip}&limit=${limit}`);
return { success: true, data: response.data };
} catch (error) {
return {
success: false,
error: error.response?.data?.detail || 'Failed to get reports list'
};
}
};
Custom Hooks
Authentication Hook
import { useState, useEffect, useContext, createContext } from 'react';
import { getCurrentUser, isAuthenticated } from '../services/auth';
// Create auth context
const AuthContext = createContext(null);
// Auth provider component
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Check if user is authenticated and fetch user data
const fetchUser = async () => {
if (isAuthenticated()) {
try {
setLoading(true);
const result = await getCurrentUser();
if (result.success) {
setUser(result.data);
} else {
setError(result.error);
}
} catch (err) {
setError('Failed to fetch user data');
} finally {
setLoading(false);
}
} else {
setLoading(false);
}
};
fetchUser();
}, []);
// Return provider with auth context
return (
<AuthContext.Provider value={{ user, loading, error, setUser }}>
{children}
</AuthContext.Provider>
);
};
// Custom hook to use auth context
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === null) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
Search Hook
import { useState } from 'react';
import { executeSearch, getAvailableEngines } from '../services/search';
export const useSearch = () => {
const [results, setResults] = useState([]);
const [engines, setEngines] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Load available search engines
const loadEngines = async () => {
try {
setLoading(true);
const result = await getAvailableEngines();
if (result.success) {
setEngines(result.data);
} else {
setError(result.error);
}
} catch (err) {
setError('Failed to load search engines');
} finally {
setLoading(false);
}
};
// Execute a search
const search = async (searchParams) => {
try {
setLoading(true);
setError(null);
const result = await executeSearch(searchParams);
if (result.success) {
setResults(result.data.results);
return result.data;
} else {
setError(result.error);
return null;
}
} catch (err) {
setError('Failed to execute search');
return null;
} finally {
setLoading(false);
}
};
return {
results,
engines,
loading,
error,
search,
loadEngines,
};
};
Implementation Phases
Phase 1: Project Setup & Core Components (Week 1)
- Set up Next.js project
- Configure Tailwind CSS
- Implement common UI components
- Create layout components
Phase 2: Authentication & API Integration (Week 1-2)
- Implement authentication components
- Create API service layer
- Implement custom hooks
- Set up protected routes
Phase 3: Search Functionality (Week 2)
- Implement search form
- Create search results display
- Add filtering and sorting
- Implement search engine selection
Phase 4: Report Generation & Management (Week 2-3)
- Implement report generation form
- Create report viewer with markdown rendering
- Add report management interface
- Implement download functionality
Phase 5: Testing & Refinement (Week 3)
- Write component tests
- Perform cross-browser testing
- Add responsive design improvements
- Optimize performance
Phase 6: Deployment & Documentation (Week 3-4)
- Set up deployment configuration
- Create user documentation
- Add inline help and tooltips
- Perform final testing
Dependencies
{
"dependencies": {
"next": "^13.5.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.5.1",
"react-markdown": "^9.0.0",
"react-query": "^3.39.3",
"tailwindcss": "^3.3.3",
"postcss": "^8.4.31",
"autoprefixer": "^10.4.16",
"jose": "^4.14.6"
},
"devDependencies": {
"eslint": "^8.51.0",
"eslint-config-next": "^13.5.4",
"typescript": "^5.2.2",
"@types/react": "^18.2.28",
"@types/node": "^20.8.6",
"jest": "^29.7.0",
"@testing-library/react": "^14.0.0",
"@testing-library/jest-dom": "^6.1.4"
}
}
Accessibility Considerations
The React frontend will be built with accessibility in mind:
- Semantic HTML: Use proper HTML elements for their intended purpose
- ARIA Attributes: Add ARIA attributes where necessary
- Keyboard Navigation: Ensure all interactive elements are keyboard accessible
- Focus Management: Properly manage focus, especially in modals and dialogs
- Color Contrast: Ensure sufficient color contrast for text and UI elements
- Screen Reader Support: Test with screen readers to ensure compatibility
Performance Optimization
To ensure optimal performance:
- Code Splitting: Use Next.js code splitting to reduce initial bundle size
- Lazy Loading: Implement lazy loading for components not needed immediately
- Memoization: Use React.memo and useMemo to prevent unnecessary re-renders
- Image Optimization: Use Next.js image optimization for faster loading
- API Response Caching: Cache API responses with React Query
- Bundle Analysis: Regularly analyze bundle size to identify improvements
Conclusion
This implementation plan provides a structured approach to creating a modern React frontend for the sim-search project. By following this plan, we will create a user-friendly, accessible, and feature-rich interface that leverages the power of the new FastAPI backend.
The component-based architecture ensures reusability and maintainability, while the use of modern React patterns and hooks simplifies state management and side effects. The integration with the FastAPI backend is handled through a clean service layer, making it easy to adapt to changes in the API.
With this implementation, users will have a much improved experience compared to the current Gradio interface, with better search capabilities, more advanced report generation options, and a more intuitive interface for managing their research.