572 lines
17 KiB
Markdown
572 lines
17 KiB
Markdown
# 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
|
|
|
|
1. **Next.js Framework**
|
|
- Server-side rendering for improved SEO
|
|
- API routes for backend proxying if needed
|
|
- Static site generation for performance
|
|
|
|
2. **Component Library**
|
|
- Modular React components
|
|
- Reusable UI elements
|
|
- Styling with Tailwind CSS
|
|
|
|
3. **State Management**
|
|
- React Query for server state
|
|
- Context API for application state
|
|
- Form state management
|
|
|
|
4. **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
|
|
```jsx
|
|
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
|
|
```jsx
|
|
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
|
|
```jsx
|
|
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
|
|
```jsx
|
|
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
|
|
```javascript
|
|
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
|
|
```javascript
|
|
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
|
|
```javascript
|
|
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
|
|
```javascript
|
|
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
|
|
```javascript
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
1. **Semantic HTML**: Use proper HTML elements for their intended purpose
|
|
2. **ARIA Attributes**: Add ARIA attributes where necessary
|
|
3. **Keyboard Navigation**: Ensure all interactive elements are keyboard accessible
|
|
4. **Focus Management**: Properly manage focus, especially in modals and dialogs
|
|
5. **Color Contrast**: Ensure sufficient color contrast for text and UI elements
|
|
6. **Screen Reader Support**: Test with screen readers to ensure compatibility
|
|
|
|
## Performance Optimization
|
|
|
|
To ensure optimal performance:
|
|
|
|
1. **Code Splitting**: Use Next.js code splitting to reduce initial bundle size
|
|
2. **Lazy Loading**: Implement lazy loading for components not needed immediately
|
|
3. **Memoization**: Use React.memo and useMemo to prevent unnecessary re-renders
|
|
4. **Image Optimization**: Use Next.js image optimization for faster loading
|
|
5. **API Response Caching**: Cache API responses with React Query
|
|
6. **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.
|