ira/.note/react_implementation_plan.md

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

  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

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:

  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.