Implement API and React frontend specifications
This commit adds: 1. Comprehensive FastAPI routes for search, report, and authentication 2. Fixed Pydantic model compatibility issues with model_dump() 3. Added detailed API specification documentation in api_specification.md 4. Added React implementation plan with component designs and architecture 5. Improved test coverage for API endpoints 6. Added progress tracking for report generation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b1d0fa350a
commit
79d2d93af9
|
@ -0,0 +1,587 @@
|
|||
# Sim-Search API Specification
|
||||
|
||||
This document provides a comprehensive guide for frontend developers to integrate with the Sim-Search API. The API offers intelligent research capabilities, including query processing, search execution across multiple engines, and report generation.
|
||||
|
||||
## API Base URL
|
||||
|
||||
```
|
||||
/api/v1
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The API uses OAuth2 with Bearer token authentication. All API endpoints except for authentication endpoints require a valid Bearer token.
|
||||
|
||||
### Register a New User
|
||||
|
||||
```
|
||||
POST /api/v1/auth/register
|
||||
```
|
||||
|
||||
Register a new user account.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "password123",
|
||||
"full_name": "User Name",
|
||||
"is_active": true,
|
||||
"is_superuser": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"id": "user-uuid",
|
||||
"email": "user@example.com",
|
||||
"full_name": "User Name",
|
||||
"is_active": true,
|
||||
"is_superuser": false
|
||||
}
|
||||
```
|
||||
|
||||
### Login to Get Access Token
|
||||
|
||||
```
|
||||
POST /api/v1/auth/token
|
||||
```
|
||||
|
||||
Obtain an access token for API authentication.
|
||||
|
||||
**Request Body (form data)**:
|
||||
```
|
||||
username=user@example.com
|
||||
password=password123
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||
"token_type": "bearer"
|
||||
}
|
||||
```
|
||||
|
||||
## Query Processing
|
||||
|
||||
### Process a Query
|
||||
|
||||
```
|
||||
POST /api/v1/query/process
|
||||
```
|
||||
|
||||
Process a search query to enhance and structure it for better search results.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"query": "What are the latest advancements in quantum computing?"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"original_query": "What are the latest advancements in quantum computing?",
|
||||
"structured_query": {
|
||||
"original_query": "What are the latest advancements in quantum computing?",
|
||||
"enhanced_query": "What are the recent breakthroughs and developments in quantum computing technology, algorithms, and applications in the past 2 years?",
|
||||
"type": "exploratory",
|
||||
"intent": "research",
|
||||
"domain": "academic",
|
||||
"confidence": 0.95,
|
||||
"reasoning": "This query is asking about recent developments in a scientific field, which is typical of academic research.",
|
||||
"entities": ["quantum computing", "advancements"],
|
||||
"sub_questions": [
|
||||
{
|
||||
"sub_question": "What are the latest hardware advancements in quantum computing?",
|
||||
"aspect": "hardware",
|
||||
"priority": 0.9
|
||||
},
|
||||
{
|
||||
"sub_question": "What are the recent algorithmic breakthroughs in quantum computing?",
|
||||
"aspect": "algorithms",
|
||||
"priority": 0.8
|
||||
}
|
||||
],
|
||||
"search_queries": {
|
||||
"google": "latest advancements in quantum computing 2024",
|
||||
"scholar": "recent quantum computing breakthroughs",
|
||||
"arxiv": "quantum computing hardware algorithms"
|
||||
},
|
||||
"is_academic": true,
|
||||
"is_code": false,
|
||||
"is_current_events": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Classify a Query
|
||||
|
||||
```
|
||||
POST /api/v1/query/classify
|
||||
```
|
||||
|
||||
Classify a query by type and intent.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"query": "What are the latest advancements in quantum computing?"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"original_query": "What are the latest advancements in quantum computing?",
|
||||
"structured_query": {
|
||||
"original_query": "What are the latest advancements in quantum computing?",
|
||||
"type": "exploratory",
|
||||
"domain": "academic",
|
||||
"confidence": 0.95
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Search Execution
|
||||
|
||||
### Get Available Search Engines
|
||||
|
||||
```
|
||||
GET /api/v1/search/engines
|
||||
```
|
||||
|
||||
Get a list of available search engines.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
["google", "arxiv", "scholar", "news", "openalex", "core", "github", "stackexchange"]
|
||||
```
|
||||
|
||||
### Execute a Search
|
||||
|
||||
```
|
||||
POST /api/v1/search/execute
|
||||
```
|
||||
|
||||
Execute a search with the given parameters.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"structured_query": {
|
||||
"original_query": "What are the environmental impacts of electric vehicles?",
|
||||
"enhanced_query": "What are the environmental impacts of electric vehicles?",
|
||||
"type": "factual",
|
||||
"domain": "environmental"
|
||||
},
|
||||
"search_engines": ["google", "arxiv"],
|
||||
"num_results": 5,
|
||||
"timeout": 30
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"search_id": "search-uuid",
|
||||
"query": "What are the environmental impacts of electric vehicles?",
|
||||
"enhanced_query": "What are the environmental impacts of electric vehicles?",
|
||||
"results": {
|
||||
"google": [
|
||||
{
|
||||
"title": "Environmental Impacts of Electric Vehicles",
|
||||
"url": "https://example.com/article1",
|
||||
"snippet": "Electric vehicles have several environmental impacts including...",
|
||||
"source": "google",
|
||||
"score": 0.95
|
||||
}
|
||||
],
|
||||
"arxiv": [
|
||||
{
|
||||
"title": "Lifecycle Analysis of Electric Vehicle Environmental Impact",
|
||||
"url": "http://arxiv.org/abs/paper123",
|
||||
"pdf_url": "http://arxiv.org/pdf/paper123",
|
||||
"snippet": "This paper analyzes the complete lifecycle environmental impact of electric vehicles...",
|
||||
"source": "arxiv",
|
||||
"authors": ["Researcher Name1", "Researcher Name2"],
|
||||
"arxiv_id": "paper123",
|
||||
"categories": ["cs.CY", "eess.SY"],
|
||||
"published_date": "2023-01-15T10:30:00Z",
|
||||
"score": 0.92
|
||||
}
|
||||
]
|
||||
},
|
||||
"total_results": 2,
|
||||
"execution_time": 1.25,
|
||||
"timestamp": "2024-03-20T14:25:30Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Search History
|
||||
|
||||
```
|
||||
GET /api/v1/search/history
|
||||
```
|
||||
|
||||
Get the user's search history.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Query Parameters**:
|
||||
- skip (optional, default: 0): Number of records to skip
|
||||
- limit (optional, default: 100): Maximum number of records to return
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"searches": [
|
||||
{
|
||||
"id": "search-uuid",
|
||||
"query": "What are the environmental impacts of electric vehicles?",
|
||||
"enhanced_query": "What are the environmental impacts of electric vehicles?",
|
||||
"query_type": "factual",
|
||||
"engines": "google,arxiv",
|
||||
"results_count": 10,
|
||||
"created_at": "2024-03-20T14:25:30Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Get Search Results
|
||||
|
||||
```
|
||||
GET /api/v1/search/{search_id}
|
||||
```
|
||||
|
||||
Get results for a specific search.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Path Parameters**:
|
||||
- search_id: ID of the search
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"search_id": "search-uuid",
|
||||
"query": "What are the environmental impacts of electric vehicles?",
|
||||
"enhanced_query": "What are the environmental impacts of electric vehicles?",
|
||||
"results": {
|
||||
"google": [
|
||||
{
|
||||
"title": "Environmental Impacts of Electric Vehicles",
|
||||
"url": "https://example.com/article1",
|
||||
"snippet": "Electric vehicles have several environmental impacts including...",
|
||||
"source": "google",
|
||||
"score": 0.95
|
||||
}
|
||||
],
|
||||
"arxiv": [
|
||||
{
|
||||
"title": "Lifecycle Analysis of Electric Vehicle Environmental Impact",
|
||||
"url": "http://arxiv.org/abs/paper123",
|
||||
"pdf_url": "http://arxiv.org/pdf/paper123",
|
||||
"snippet": "This paper analyzes the complete lifecycle environmental impact of electric vehicles...",
|
||||
"source": "arxiv",
|
||||
"authors": ["Researcher Name1", "Researcher Name2"],
|
||||
"arxiv_id": "paper123",
|
||||
"categories": ["cs.CY", "eess.SY"],
|
||||
"published_date": "2023-01-15T10:30:00Z",
|
||||
"score": 0.92
|
||||
}
|
||||
]
|
||||
},
|
||||
"total_results": 2,
|
||||
"execution_time": 0.0
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Search
|
||||
|
||||
```
|
||||
DELETE /api/v1/search/{search_id}
|
||||
```
|
||||
|
||||
Delete a search from history.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Path Parameters**:
|
||||
- search_id: ID of the search to delete
|
||||
|
||||
**Response** (204 No Content)
|
||||
|
||||
## Report Generation
|
||||
|
||||
### Generate a Report
|
||||
|
||||
```
|
||||
POST /api/v1/report/generate
|
||||
```
|
||||
|
||||
Generate a report from search results.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"search_id": "search-uuid",
|
||||
"query": "What are the environmental impacts of electric vehicles?",
|
||||
"detail_level": "standard",
|
||||
"query_type": "comparative",
|
||||
"model": "llama-3.1-8b-instant",
|
||||
"title": "Environmental Impacts of Electric Vehicles"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"id": "report-uuid",
|
||||
"user_id": "user-uuid",
|
||||
"search_id": "search-uuid",
|
||||
"title": "Environmental Impacts of Electric Vehicles",
|
||||
"content": "Report generation in progress...",
|
||||
"detail_level": "standard",
|
||||
"query_type": "comparative",
|
||||
"model_used": "llama-3.1-8b-instant",
|
||||
"created_at": "2024-03-20T14:30:00Z",
|
||||
"updated_at": "2024-03-20T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Report Generation Progress
|
||||
|
||||
```
|
||||
GET /api/v1/report/{report_id}/progress
|
||||
```
|
||||
|
||||
Get the progress of a report generation.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Path Parameters**:
|
||||
- report_id: ID of the report
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"report_id": "report-uuid",
|
||||
"progress": 0.75,
|
||||
"status": "Processing chunk 3/4...",
|
||||
"current_chunk": 3,
|
||||
"total_chunks": 4,
|
||||
"current_report": "The environmental impacts of electric vehicles include..."
|
||||
}
|
||||
```
|
||||
|
||||
### Get Report List
|
||||
|
||||
```
|
||||
GET /api/v1/report/list
|
||||
```
|
||||
|
||||
Get a list of user's reports.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Query Parameters**:
|
||||
- skip (optional, default: 0): Number of records to skip
|
||||
- limit (optional, default: 100): Maximum number of records to return
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"reports": [
|
||||
{
|
||||
"id": "report-uuid",
|
||||
"user_id": "user-uuid",
|
||||
"search_id": "search-uuid",
|
||||
"title": "Environmental Impacts of Electric Vehicles",
|
||||
"content": "# Environmental Impacts of Electric Vehicles\n\n## Introduction\n\nElectric vehicles (EVs) have gained popularity...",
|
||||
"detail_level": "standard",
|
||||
"query_type": "comparative",
|
||||
"model_used": "llama-3.1-8b-instant",
|
||||
"created_at": "2024-03-20T14:30:00Z",
|
||||
"updated_at": "2024-03-20T14:35:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Get Report
|
||||
|
||||
```
|
||||
GET /api/v1/report/{report_id}
|
||||
```
|
||||
|
||||
Get a specific report.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Path Parameters**:
|
||||
- report_id: ID of the report
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"id": "report-uuid",
|
||||
"user_id": "user-uuid",
|
||||
"search_id": "search-uuid",
|
||||
"title": "Environmental Impacts of Electric Vehicles",
|
||||
"content": "# Environmental Impacts of Electric Vehicles\n\n## Introduction\n\nElectric vehicles (EVs) have gained popularity...",
|
||||
"detail_level": "standard",
|
||||
"query_type": "comparative",
|
||||
"model_used": "llama-3.1-8b-instant",
|
||||
"created_at": "2024-03-20T14:30:00Z",
|
||||
"updated_at": "2024-03-20T14:35:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Download Report
|
||||
|
||||
```
|
||||
GET /api/v1/report/{report_id}/download
|
||||
```
|
||||
|
||||
Download a report in the specified format.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Path Parameters**:
|
||||
- report_id: ID of the report
|
||||
|
||||
**Query Parameters**:
|
||||
- format (optional, default: "markdown"): Format of the report (markdown, html, pdf)
|
||||
|
||||
**Response** (200 OK):
|
||||
- Content-Type: application/octet-stream
|
||||
- Content-Disposition: attachment; filename="report_{report_id}.{format}"
|
||||
- Binary file content
|
||||
|
||||
### Delete Report
|
||||
|
||||
```
|
||||
DELETE /api/v1/report/{report_id}
|
||||
```
|
||||
|
||||
Delete a report.
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Bearer {access_token}
|
||||
|
||||
**Path Parameters**:
|
||||
- report_id: ID of the report to delete
|
||||
|
||||
**Response** (204 No Content)
|
||||
|
||||
## Error Handling
|
||||
|
||||
The API returns standard HTTP status codes to indicate the success or failure of a request.
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
- 400 Bad Request: The request was invalid or cannot be served
|
||||
- 401 Unauthorized: Authentication is required or has failed
|
||||
- 403 Forbidden: The authenticated user doesn't have the necessary permissions
|
||||
- 404 Not Found: The requested resource was not found
|
||||
- 422 Unprocessable Entity: The request data failed validation
|
||||
- 500 Internal Server Error: An error occurred on the server
|
||||
|
||||
### Error Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Error message explaining what went wrong"
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices for Frontend Integration
|
||||
|
||||
1. **Authentication Flow**:
|
||||
- Implement a login form that sends credentials to `/api/v1/auth/token`
|
||||
- Store the received token securely (HTTP-only cookies or secure storage)
|
||||
- Include the token in the Authorization header for all subsequent requests
|
||||
- Implement token expiration handling and refresh mechanism
|
||||
|
||||
2. **Query Processing Workflow**:
|
||||
- Allow users to enter natural language queries
|
||||
- Use the `/api/v1/query/process` endpoint to enhance the query
|
||||
- Display the enhanced query to the user for confirmation
|
||||
|
||||
3. **Search Execution**:
|
||||
- Use the processed query for search execution
|
||||
- Allow users to select which search engines to use
|
||||
- Implement a loading state while waiting for search results
|
||||
- Display search results grouped by search engine
|
||||
|
||||
4. **Report Generation**:
|
||||
- Allow users to generate reports from search results
|
||||
- Provide options for detail level and report type
|
||||
- Implement progress tracking using the progress endpoint
|
||||
- Allow users to download reports in different formats
|
||||
|
||||
5. **Error Handling**:
|
||||
- Implement proper error handling for API responses
|
||||
- Display meaningful error messages to users
|
||||
- Implement retry mechanisms for transient errors
|
||||
|
||||
## Available Search Engines
|
||||
|
||||
- **google**: General web search
|
||||
- **arxiv**: Academic papers from arXiv
|
||||
- **scholar**: Academic papers from various sources
|
||||
- **news**: News articles
|
||||
- **openalex**: Open access academic content
|
||||
- **core**: Open access research papers
|
||||
- **github**: Code repositories
|
||||
- **stackexchange**: Q&A from Stack Exchange network
|
||||
|
||||
## Report Detail Levels
|
||||
|
||||
- **brief**: Short summary (default model: llama-3.1-8b-instant)
|
||||
- **standard**: Comprehensive overview (default model: llama-3.1-8b-instant)
|
||||
- **detailed**: In-depth analysis (default model: llama-3.3-70b-versatile)
|
||||
- **comprehensive**: Extensive research report (default model: llama-3.3-70b-versatile)
|
||||
|
||||
## Query Types
|
||||
|
||||
- **factual**: Seeking facts or information
|
||||
- **comparative**: Comparing multiple items or concepts
|
||||
- **exploratory**: Open-ended exploration of a topic
|
||||
- **procedural**: How to do something
|
||||
- **causal**: Seeking cause-effect relationships
|
||||
|
||||
## Models
|
||||
|
||||
- **llama-3.1-8b-instant**: Fast, lightweight model
|
||||
- **llama-3.3-70b-versatile**: High-quality, comprehensive model
|
||||
- **Other models may be available based on server configuration**
|
|
@ -0,0 +1,571 @@
|
|||
# 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.
|
|
@ -169,7 +169,7 @@ async def delete_report(
|
|||
report_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db),
|
||||
) -> Any:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a report.
|
||||
|
||||
|
@ -193,8 +193,6 @@ async def delete_report(
|
|||
|
||||
db.delete(report)
|
||||
db.commit()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@router.get("/{report_id}/progress", response_model=ReportProgress)
|
||||
|
|
|
@ -37,16 +37,25 @@ async def execute_search(
|
|||
Search results
|
||||
"""
|
||||
try:
|
||||
# Get the structured query from the input
|
||||
structured_query = search_in.structured_query.model_dump() if search_in.structured_query else {}
|
||||
|
||||
# Print for debugging
|
||||
print(f"Executing search with structured_query: {structured_query}")
|
||||
|
||||
# Call the search service
|
||||
search_results = await search_service.execute_search(
|
||||
search_in.structured_query.model_dump(),
|
||||
search_in.search_engines,
|
||||
search_in.num_results,
|
||||
search_in.timeout,
|
||||
current_user.id,
|
||||
db,
|
||||
structured_query=structured_query, # Explicitly use keyword argument
|
||||
search_engines=search_in.search_engines,
|
||||
num_results=search_in.num_results,
|
||||
timeout=search_in.timeout,
|
||||
user_id=current_user.id,
|
||||
db=db,
|
||||
)
|
||||
return search_results
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error executing search: {str(e)}",
|
||||
|
|
|
@ -8,7 +8,8 @@ import os
|
|||
import secrets
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
|
||||
from pydantic import AnyHttpUrl, BaseSettings, validator
|
||||
from pydantic import AnyHttpUrl, field_validator
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
@ -28,7 +29,8 @@ class Settings(BaseSettings):
|
|||
# CORS settings
|
||||
CORS_ORIGINS: List[str] = ["*"]
|
||||
|
||||
@validator("CORS_ORIGINS", pre=True)
|
||||
@field_validator("CORS_ORIGINS", mode="before")
|
||||
@classmethod
|
||||
def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
|
||||
"""Parse CORS origins from string or list."""
|
||||
if isinstance(v, str) and not v.startswith("["):
|
||||
|
@ -53,10 +55,10 @@ class Settings(BaseSettings):
|
|||
"comprehensive": "llama-3.3-70b-versatile"
|
||||
}
|
||||
|
||||
class Config:
|
||||
"""Pydantic config."""
|
||||
case_sensitive = True
|
||||
env_file = ".env"
|
||||
model_config = {
|
||||
"case_sensitive": True,
|
||||
"env_file": ".env",
|
||||
}
|
||||
|
||||
|
||||
# Create settings instance
|
||||
|
|
|
@ -20,7 +20,7 @@ class SearchResult(BaseModel):
|
|||
snippet: str
|
||||
source: str
|
||||
score: Optional[float] = None
|
||||
authors: Optional[str] = None
|
||||
authors: Optional[Any] = None # Can be string or list of strings
|
||||
year: Optional[str] = None
|
||||
pdf_url: Optional[str] = None
|
||||
arxiv_id: Optional[str] = None
|
||||
|
|
|
@ -25,7 +25,7 @@ sys.path.append(str(sim_search_path))
|
|||
# Import sim-search components
|
||||
from report.report_generator import get_report_generator, initialize_report_generator
|
||||
from report.report_detail_levels import get_report_detail_level_manager
|
||||
from services.search_service import SearchService
|
||||
from app.services.search_service import SearchService
|
||||
|
||||
|
||||
class ReportService:
|
||||
|
|
|
@ -72,15 +72,35 @@ class SearchService:
|
|||
# Start timing
|
||||
start_time = time.time()
|
||||
|
||||
# Make sure structured_query is not None
|
||||
if structured_query is None:
|
||||
structured_query = {}
|
||||
|
||||
# Add search engines if not specified
|
||||
if not search_engines:
|
||||
search_engines = self.search_executor.get_available_search_engines()
|
||||
structured_query["search_engines"] = search_engines
|
||||
|
||||
# Execute the search
|
||||
# Ensure all required fields are present
|
||||
original_query = structured_query.get("original_query", "")
|
||||
|
||||
# Add raw_query field (required by search_executor)
|
||||
structured_query["raw_query"] = structured_query.get("raw_query", original_query)
|
||||
|
||||
# Add enhanced_query if missing
|
||||
if "enhanced_query" not in structured_query:
|
||||
structured_query["enhanced_query"] = original_query
|
||||
|
||||
# Make sure search_queries is not None (required by search_executor)
|
||||
if "search_queries" not in structured_query or structured_query["search_queries"] is None:
|
||||
structured_query["search_queries"] = {}
|
||||
|
||||
# Execute the search with the fixed structured_query
|
||||
search_results = self.search_executor.execute_search(
|
||||
structured_query=structured_query,
|
||||
num_results=num_results
|
||||
search_engines=search_engines,
|
||||
num_results=num_results,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Calculate execution time
|
||||
|
@ -135,12 +155,28 @@ class SearchService:
|
|||
# Parse engines string
|
||||
engines = search.engines.split(",") if search.engines else []
|
||||
|
||||
# Get results from the database - ensure they are in correct format
|
||||
results = {}
|
||||
|
||||
# Check if results are already in engine->list format or just a flat list
|
||||
if isinstance(search.results, dict):
|
||||
# Already in the correct format
|
||||
results = search.results
|
||||
else:
|
||||
# Need to convert from flat list to engine->list format
|
||||
# Group by source
|
||||
for result in search.results:
|
||||
source = result.get("source", "unknown")
|
||||
if source not in results:
|
||||
results[source] = []
|
||||
results[source].append(result)
|
||||
|
||||
# Format the response
|
||||
return {
|
||||
"search_id": search.id,
|
||||
"query": search.query,
|
||||
"enhanced_query": search.enhanced_query,
|
||||
"results": search.results,
|
||||
"results": results,
|
||||
"total_results": search.results_count,
|
||||
"execution_time": 0.0, # Not available for stored searches
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ test_user_full_name = "Test User"
|
|||
@pytest.fixture(scope="module")
|
||||
def setup_database():
|
||||
"""Set up the test database."""
|
||||
# Clean up any existing database
|
||||
if os.path.exists("./test.db"):
|
||||
os.remove("./test.db")
|
||||
|
||||
# Create tables
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
|
@ -67,13 +71,21 @@ def setup_database():
|
|||
is_superuser=False,
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"Error creating test user: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
yield
|
||||
|
||||
# Clean up
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
if os.path.exists("./test.db"):
|
||||
os.remove("./test.db")
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def auth_token(setup_database):
|
||||
|
@ -358,15 +370,41 @@ def test_get_report(auth_token):
|
|||
|
||||
def test_download_report(auth_token):
|
||||
"""Test downloading a report."""
|
||||
# First, get the list of reports to get a report_id
|
||||
response = client.get(
|
||||
f"{settings.API_V1_STR}/report/list",
|
||||
# First, execute a search to get a search_id
|
||||
response = client.post(
|
||||
f"{settings.API_V1_STR}/search/execute",
|
||||
json={
|
||||
"structured_query": {
|
||||
"original_query": "What are the environmental impacts of electric vehicles?",
|
||||
"enhanced_query": "What are the environmental impacts of electric vehicles?",
|
||||
"type": "comparative",
|
||||
"domain": "environmental,economic",
|
||||
},
|
||||
"search_engines": ["serper"],
|
||||
"num_results": 2,
|
||||
"timeout": 10,
|
||||
},
|
||||
headers={"Authorization": f"Bearer {auth_token}"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
list_data = response.json()
|
||||
assert len(list_data["reports"]) > 0
|
||||
report_id = list_data["reports"][0]["id"]
|
||||
search_data = response.json()
|
||||
search_id = search_data["search_id"]
|
||||
|
||||
# Now generate a report
|
||||
response = client.post(
|
||||
f"{settings.API_V1_STR}/report/generate",
|
||||
json={
|
||||
"search_id": search_id,
|
||||
"query": "What are the environmental impacts of electric vehicles?",
|
||||
"detail_level": "brief",
|
||||
"query_type": "comparative",
|
||||
"model": "llama-3.1-8b-instant",
|
||||
},
|
||||
headers={"Authorization": f"Bearer {auth_token}"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
report_data = response.json()
|
||||
report_id = report_data["id"]
|
||||
|
||||
# Now download the report in markdown format
|
||||
response = client.get(
|
||||
|
@ -375,7 +413,7 @@ def test_download_report(auth_token):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/octet-stream"
|
||||
assert response.headers["content-disposition"] == f'filename="report_{report_id}.markdown"'
|
||||
assert response.headers["content-disposition"] == f'attachment; filename="report_{report_id}.markdown"'
|
||||
|
||||
# Now download the report in HTML format
|
||||
response = client.get(
|
||||
|
@ -384,7 +422,7 @@ def test_download_report(auth_token):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/octet-stream"
|
||||
assert response.headers["content-disposition"] == f'filename="report_{report_id}.html"'
|
||||
assert response.headers["content-disposition"] == f'attachment; filename="report_{report_id}.html"'
|
||||
|
||||
def test_delete_report(auth_token):
|
||||
"""Test deleting a report."""
|
||||
|
|
Loading…
Reference in New Issue