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,
|
report_id: str,
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> Any:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a report.
|
Delete a report.
|
||||||
|
|
||||||
|
@ -194,8 +194,6 @@ async def delete_report(
|
||||||
db.delete(report)
|
db.delete(report)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{report_id}/progress", response_model=ReportProgress)
|
@router.get("/{report_id}/progress", response_model=ReportProgress)
|
||||||
async def get_report_progress(
|
async def get_report_progress(
|
||||||
|
|
|
@ -37,16 +37,25 @@ async def execute_search(
|
||||||
Search results
|
Search results
|
||||||
"""
|
"""
|
||||||
try:
|
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_results = await search_service.execute_search(
|
||||||
search_in.structured_query.model_dump(),
|
structured_query=structured_query, # Explicitly use keyword argument
|
||||||
search_in.search_engines,
|
search_engines=search_in.search_engines,
|
||||||
search_in.num_results,
|
num_results=search_in.num_results,
|
||||||
search_in.timeout,
|
timeout=search_in.timeout,
|
||||||
current_user.id,
|
user_id=current_user.id,
|
||||||
db,
|
db=db,
|
||||||
)
|
)
|
||||||
return search_results
|
return search_results
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail=f"Error executing search: {str(e)}",
|
detail=f"Error executing search: {str(e)}",
|
||||||
|
|
|
@ -8,7 +8,8 @@ import os
|
||||||
import secrets
|
import secrets
|
||||||
from typing import List, Optional, Dict, Any, Union
|
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):
|
class Settings(BaseSettings):
|
||||||
|
@ -28,7 +29,8 @@ class Settings(BaseSettings):
|
||||||
# CORS settings
|
# CORS settings
|
||||||
CORS_ORIGINS: List[str] = ["*"]
|
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]:
|
def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
|
||||||
"""Parse CORS origins from string or list."""
|
"""Parse CORS origins from string or list."""
|
||||||
if isinstance(v, str) and not v.startswith("["):
|
if isinstance(v, str) and not v.startswith("["):
|
||||||
|
@ -53,10 +55,10 @@ class Settings(BaseSettings):
|
||||||
"comprehensive": "llama-3.3-70b-versatile"
|
"comprehensive": "llama-3.3-70b-versatile"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Config:
|
model_config = {
|
||||||
"""Pydantic config."""
|
"case_sensitive": True,
|
||||||
case_sensitive = True
|
"env_file": ".env",
|
||||||
env_file = ".env"
|
}
|
||||||
|
|
||||||
|
|
||||||
# Create settings instance
|
# Create settings instance
|
||||||
|
|
|
@ -20,7 +20,7 @@ class SearchResult(BaseModel):
|
||||||
snippet: str
|
snippet: str
|
||||||
source: str
|
source: str
|
||||||
score: Optional[float] = None
|
score: Optional[float] = None
|
||||||
authors: Optional[str] = None
|
authors: Optional[Any] = None # Can be string or list of strings
|
||||||
year: Optional[str] = None
|
year: Optional[str] = None
|
||||||
pdf_url: Optional[str] = None
|
pdf_url: Optional[str] = None
|
||||||
arxiv_id: Optional[str] = None
|
arxiv_id: Optional[str] = None
|
||||||
|
|
|
@ -25,7 +25,7 @@ sys.path.append(str(sim_search_path))
|
||||||
# Import sim-search components
|
# Import sim-search components
|
||||||
from report.report_generator import get_report_generator, initialize_report_generator
|
from report.report_generator import get_report_generator, initialize_report_generator
|
||||||
from report.report_detail_levels import get_report_detail_level_manager
|
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:
|
class ReportService:
|
||||||
|
|
|
@ -72,15 +72,35 @@ class SearchService:
|
||||||
# Start timing
|
# Start timing
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
|
# Make sure structured_query is not None
|
||||||
|
if structured_query is None:
|
||||||
|
structured_query = {}
|
||||||
|
|
||||||
# Add search engines if not specified
|
# Add search engines if not specified
|
||||||
if not search_engines:
|
if not search_engines:
|
||||||
search_engines = self.search_executor.get_available_search_engines()
|
search_engines = self.search_executor.get_available_search_engines()
|
||||||
structured_query["search_engines"] = 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(
|
search_results = self.search_executor.execute_search(
|
||||||
structured_query=structured_query,
|
structured_query=structured_query,
|
||||||
num_results=num_results
|
search_engines=search_engines,
|
||||||
|
num_results=num_results,
|
||||||
|
timeout=timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate execution time
|
# Calculate execution time
|
||||||
|
@ -135,12 +155,28 @@ class SearchService:
|
||||||
# Parse engines string
|
# Parse engines string
|
||||||
engines = search.engines.split(",") if search.engines else []
|
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
|
# Format the response
|
||||||
return {
|
return {
|
||||||
"search_id": search.id,
|
"search_id": search.id,
|
||||||
"query": search.query,
|
"query": search.query,
|
||||||
"enhanced_query": search.enhanced_query,
|
"enhanced_query": search.enhanced_query,
|
||||||
"results": search.results,
|
"results": results,
|
||||||
"total_results": search.results_count,
|
"total_results": search.results_count,
|
||||||
"execution_time": 0.0, # Not available for stored searches
|
"execution_time": 0.0, # Not available for stored searches
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,10 @@ test_user_full_name = "Test User"
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def setup_database():
|
def setup_database():
|
||||||
"""Set up the test database."""
|
"""Set up the test database."""
|
||||||
|
# Clean up any existing database
|
||||||
|
if os.path.exists("./test.db"):
|
||||||
|
os.remove("./test.db")
|
||||||
|
|
||||||
# Create tables
|
# Create tables
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
@ -67,13 +71,21 @@ def setup_database():
|
||||||
is_superuser=False,
|
is_superuser=False,
|
||||||
)
|
)
|
||||||
db.add(user)
|
db.add(user)
|
||||||
|
try:
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
print(f"Error creating test user: {e}")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
Base.metadata.drop_all(bind=engine)
|
Base.metadata.drop_all(bind=engine)
|
||||||
|
if os.path.exists("./test.db"):
|
||||||
|
os.remove("./test.db")
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def auth_token(setup_database):
|
def auth_token(setup_database):
|
||||||
|
@ -358,15 +370,41 @@ def test_get_report(auth_token):
|
||||||
|
|
||||||
def test_download_report(auth_token):
|
def test_download_report(auth_token):
|
||||||
"""Test downloading a report."""
|
"""Test downloading a report."""
|
||||||
# First, get the list of reports to get a report_id
|
# First, execute a search to get a search_id
|
||||||
response = client.get(
|
response = client.post(
|
||||||
f"{settings.API_V1_STR}/report/list",
|
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}"},
|
headers={"Authorization": f"Bearer {auth_token}"},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
list_data = response.json()
|
search_data = response.json()
|
||||||
assert len(list_data["reports"]) > 0
|
search_id = search_data["search_id"]
|
||||||
report_id = list_data["reports"][0]["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
|
# Now download the report in markdown format
|
||||||
response = client.get(
|
response = client.get(
|
||||||
|
@ -375,7 +413,7 @@ def test_download_report(auth_token):
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.headers["content-type"] == "application/octet-stream"
|
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
|
# Now download the report in HTML format
|
||||||
response = client.get(
|
response = client.get(
|
||||||
|
@ -384,7 +422,7 @@ def test_download_report(auth_token):
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.headers["content-type"] == "application/octet-stream"
|
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):
|
def test_delete_report(auth_token):
|
||||||
"""Test deleting a report."""
|
"""Test deleting a report."""
|
||||||
|
|
Loading…
Reference in New Issue