Your team has taken the client brief and turned it into a project specification.
Afterwards the client added a description for a new feature to be added to the project specification.
Your job is to update the project specification so that it contains the new feature information but does not lack any of the information from the original project specification.

This might include:
* details on how the app should work
* information which 3rd party packages or APIs to use or avoid
* concrete examples of API requests/responses, library usage, or other external documentation

Here is the original project specification:
---START_OF_SPEC---

**Project Description**:
The project is a web-based application that provides a chat completion service using various AI models (e.g., GPT-4, GPT-3.5-turbo). The application is built using the FastAPI framework and interacts with an external AI service to generate responses based on user input. The service supports streaming responses, allowing real-time interaction with the AI model by streaming partial results back to the client as they are generated. The application is designed to function as a middle layer between the client and an external AI service, facilitating communication and handling requests for different models.

The project is divided into two main components:
1. **API Layer** (`api.py`): This layer exposes an endpoint that accepts chat completion requests, forwards them to the external AI service, and streams the responses back to the client. It also acts as a reverse proxy, receiving client requests, processing them, and forwarding them to the appropriate backend service (defined in `providers.py`).
2. **Provider Layer** (`providers.py`): This layer is responsible for making asynchronous HTTP requests to the external AI service, handling different models, and processing the responses. It defines the specific APIs being connected to, such as the different GPT models (gpt-4o, gpt-4, gpt-3.5-turbo), and specifies the headers and request formats for each provider.

**Features**:
1. **Chat Completion Endpoint**:
   - The primary feature of the project is a POST endpoint (`/v1/chat/completions`) that accepts chat requests from clients. The requests are validated using Pydantic models and include information such as the model to use and the list of messages (conversation history).
   - The endpoint supports streaming responses, where the AI-generated messages are sent back to the client in real-time using Server-Sent Events (SSE). This allows partial results to be returned as they are generated, enhancing the interactivity of the application.

2. **Model Selection**:
   - The application supports multiple AI models, including `gpt-4`, `gpt-4o`, and `gpt-3.5-turbo`. Users can specify which model they want to use when making a chat request. Each model is associated with a different API endpoint on the external service.
   - The `providers.py` file contains logic to route requests to the appropriate external API endpoint based on the selected model.

3. **External API Integration**:
   - The application integrates with an external AI service (presumably OpenAI or a similar provider) to generate chat completions. It uses asynchronous HTTP requests to communicate with the external API and retrieve responses.
   - The `providers.py` file defines the logic for making these requests and processing the responses. It handles different content types (e.g., JSON) and extracts the relevant message content from the API's response. The file also defines the headers and request format for each provider, ensuring that requests are properly formatted for the backend services.

4. **Streaming Response Handling**:
   - The application uses FastAPI’s `StreamingResponse` to stream the AI-generated responses back to the client. This is particularly useful for long-running tasks where the AI model generates a response over time. The client receives partial results in real-time, improving the user experience.
   - The `oxy_api_stream` function in `api.py` contains the logic for streaming data. It creates a stream of data chunks and yields them one at a time, allowing large amounts of data to be sent efficiently, piece by piece, instead of all at once.

5. **CORS Support**:
   - The application includes CORS middleware, allowing cross-origin requests from any domain. This ensures that the API can be accessed from web clients hosted on different origins, such as front-end applications or third-party services.

6. **Separation of Concerns**:
   - The project is organized into two files: `api.py` and `providers.py`. The `api.py` file handles client interaction and data streaming, while `providers.py` deals with the specifics of connecting to different AI models or services. This separation ensures that the responsibilities are clearly divided, making the codebase more maintainable.

**Technical Specification**:
- **Platform/Technologies**:
  - **Backend**: The backend is built using **FastAPI**, a modern Python web framework known for its performance and ease of use with asynchronous operations.
  - **External API**: The project integrates with an external AI service for chat completions. The service is accessed via HTTP POST requests, and the responses are processed asynchronously using the `aiohttp` library.
  - **Streaming**: The project uses FastAPI’s `StreamingResponse` to stream responses back to the client in real-time. This is implemented in the `api.py` file, where the `oxy_api_stream` function streams the AI-generated messages.
  - **Asynchronous Operations**: The project makes extensive use of Python’s `asyncio` and `aiohttp` for non-blocking, asynchronous HTTP requests. This ensures that the application can handle multiple requests concurrently and efficiently.

- **Endpoints**:
  - **POST `/v1/chat/completions`**:
    - This endpoint accepts a request body containing the model name, a list of messages (conversation history), and an optional `stream` flag.
    - The request body is validated using Pydantic’s `BaseModel`.
    - If the `stream` flag is `True`, the response is streamed back to the client using Server-Sent Events (SSE). Otherwise, the full response is returned once the AI model has generated it.

- **Data Models**:
  - **ChatBody** (in `api.py`): This model defines the structure of the request body for the `/v1/chat/completions` endpoint. It includes:
    - `model`: The name of the AI model to use (e.g., `gpt-4`, `gpt-3.5-turbo`).
    - `messages`: A list of dictionaries representing the conversation history, where each dictionary contains a `role` (e.g., `user`, `assistant`) and `content`.
    - `stream`: An optional boolean flag indicating whether the response should be streamed.

  - **Message** and **ChatCompletionRequest** (in `providers.py`): These models define the structure of the messages and the overall chat completion request sent to the external AI service. The `Message` model includes:
    - `role`: The role of the message sender (e.g., `user`, `assistant`).
    - `content`: The content of the message.

- **External API Interaction**:
  - The `make_request` function in `providers.py` is responsible for sending chat requests to the external AI service. It constructs the request payload (including the model and messages) and sends it to the appropriate API endpoint based on the selected model.
  - The function handles different content types in the response (e.g., JSON, plain text) and raises an error if the external API returns an HTTP error.
  - The `extract_messages` function processes the API’s JSON response and extracts the generated message content.
  - The `HEADERS` constant in `providers.py` defines the headers for each provider, ensuring that requests are properly formatted for the backend services.

- **CORS Configuration**:
  - The application uses FastAPI’s `CORSMiddleware` to enable cross-origin requests. This is configured to allow requests from any origin (`allow_origins=["*"]`), making the API accessible from different domains.

- **Running the Application**:
  - The application can be run locally using Uvicorn, a lightning-fast ASGI server. The `api.py` file includes a `__main__` block that starts the FastAPI app on `0.0.0.0` (all network interfaces) at port `8000`.
  - To run the application, execute the following command:
    ```
    python api.py
    ```

- **Error Handling**:
  - The `oxy_api_stream` function in `api.py` includes basic error handling. If an error occurs while interacting with the external AI service, an error message is streamed back to the client.
  - The `make_request` function in `providers.py` raises an exception if the external API returns an HTTP error (e.g., 4xx or 5xx status codes). This ensures that the client is notified of any issues.

---END_OF_SPEC---

Here is the new feature description:
---FEATURE-DESCRIPTION-START---
implement these features, you'll need to set up a MongoDB database, create a user management system, implement rate limiting, and create an admin panel. Here's a high-level overview of how you could approach this:

MongoDB Setup:

Install pymongo: pip install pymongo
Set up a MongoDB database and collections for users, roles, and request logs
User Management and Roles:

Create a users collection with fields for username, password (hashed), role, and API key
Create a roles collection with predefined roles and their daily request limits
Rate Limiting:

Use a library like limits for rate limiting: pip install limits
Implement per-minute and per-day rate limiting based on user roles
Request Logging:

Create a logs collection to store user requests, responses, and IP addresses
API Modifications:

Add authentication middleware to verify API keys
Implement rate limiting checks before processing requests
Log requests and responses to the database
Admin Panel (PyQt5):

Install PyQt5: pip install PyQt5
Create a desktop application for managing users, viewing logs, and adjusting rate limits
Here's a basic implementation to get you started:

MongoDB Setup (database.py):
from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['api_database']

users_collection = db['users']
roles_collection = db['roles']
logs_collection = db['logs']

# Initialize roles if they don't exist
roles = [
    {"name": "basic", "daily_limit": 100, "minute_limit": 10},
    {"name": "premium", "daily_limit": 1000, "minute_limit": 20},
    {"name": "vip", "daily_limit": 3000, "minute_limit": 30}
]

for role in roles:
    roles_collection.update_one({"name": role["name"]}, {"$set": role}, upsert=True)
User Management (user_management.py):
import hashlib
import secrets
from database import users_collection, roles_collection

def create_user(username, password, role):
    hashed_password = hashlib.sha256(password.encode()).hexdigest()
    api_key = secrets.token_hex(16)
    user = {
        "username": username,
        "password": hashed_password,
        "role": role,
        "api_key": api_key
    }
    users_collection.insert_one(user)
    return api_key

def get_user_by_api_key(api_key):
    return users_collection.find_one({"api_key": api_key})

def get_role_limits(role_name):
    role = roles_collection.find_one({"name": role_name})
    return role["daily_limit"], role["minute_limit"]
Rate Limiting (rate_limiter.py):
from limits import strategies
from limits.storage import MemoryStorage
from database import get_role_limits

limiter = strategies.MovingWindowRateLimiter(MemoryStorage())

def check_rate_limit(api_key, role):
    daily_limit, minute_limit = get_role_limits(role)

    if not limiter.test(api_key, minute_limit, 60):
        return False, "Minute limit exceeded"

    if not limiter.test(api_key, daily_limit, 86400):
        return False, "Daily limit exceeded"

    limiter.hit(api_key, 1, 60)
    limiter.hit(api_key, 1, 86400)
    return True, "OK"
Request Logging (logger.py):
from database import logs_collection

def log_request(api_key, ip_address, request, response):
    log_entry = {
        "api_key": api_key,
        "ip_address": ip_address,
        "request": request,
        "response": response,
        "timestamp": datetime.now()
    }
    logs_collection.insert_one(log
Continue your response exactly where you left off:


gpt-4o
Add Context
⏎ Enter
_entry)


5. API Modifications (api.py):

```python
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import APIKeyHeader
from user_management import get_user_by_api_key
from rate_limiter import check_rate_limit
from logger import log_request

app = FastAPI()

api_key_header = APIKeyHeader(name="X-API-Key")

async def get_current_user(api_key: str = Depends(api_key_header)):
    user = get_user_by_api_key(api_key)
    if not user:
        raise HTTPException(status_code=401, detail="Invalid API Key")
    return user

@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest, user: dict = Depends(get_current_user)):
    # Check rate limit
    can_proceed, message = check_rate_limit(user['api_key'], user['role'])
    if not can_proceed:
        raise HTTPException(status_code=429, detail=message)

    # Process the request (your existing code here)
    response = ...  # Your existing response generation code

    # Log the request and response
    log_request(user['api_key'], request.client.host, request.dict(), response)

    return response
Admin Panel (admin_panel.py):
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QVBoxLayout, QWidget, QTableWidget, QTableWidgetItem, QPushButton, QLineEdit, QFormLayout, QComboBox
from database import users_collection, roles_collection, logs_collection

class AdminPanel(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("API Admin Panel")
        self.setGeometry(100, 100, 800, 600)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        layout = QVBoxLayout()
        central_widget.setLayout(layout)

        tabs = QTabWidget()
        layout.addWidget(tabs)

        # Users tab
        users_tab = QWidget()
        users_layout = QVBoxLayout()
        users_tab.setLayout(users_layout)

        self.users_table = QTableWidget()
        users_layout.addWidget(self.users_table)

        add_user_button = QPushButton("Add User")
        add_user_button.clicked.connect(self.add_user)
        users_layout.addWidget(add_user_button)

        tabs.addTab(users_tab, "Users")

        # Logs tab
        logs_tab = QWidget()
        logs_layout = QVBoxLayout()
        logs_tab.setLayout(logs_layout)

        self.logs_table = QTableWidget()
        logs_layout.addWidget(self.logs_table)

        tabs.addTab(logs_tab, "Logs")

        self.load_users()
        self.load_logs()

    def load_users(self):
        users = users_collection.find()
        self.users_table.setColumnCount(4)
        self.users_table.setHorizontalHeaderLabels(["Username", "Role", "API Key", "Actions"])
        self.users_table.setRowCount(users.count())

        for i, user in enumerate(users):
            self.users_table.setItem(i, 0, QTableWidgetItem(user['username']))
            self.users_table.setItem(i, 1, QTableWidgetItem(user['role']))
            self.users_table.setItem(i, 2, QTableWidgetItem(user['api_key']))

            delete_button = QPushButton("Delete")
            delete_button.clicked.connect(lambda _, u=user['username']: self.delete_user(u))
            self.users_table.setCellWidget(i, 3, delete_button)

    def load_logs(self):
        logs = logs_collection.find().sort("timestamp", -1).limit(100)
        self.logs_table.set
Continue your response exactly where you left off:


gpt-4o
Add Context
⏎ Enter
ColumnCount(5) self.logs_table.setHorizontalHeaderLabels(["API Key", "IP Address", "Request", "Response", "Timestamp"]) self.logs_table.setRowCount(logs.count())

    for i, log in enumerate(logs):
        self.logs_table.setItem(i, 0, QTableWidgetItem(log['api_key']))
        self.logs_table.setItem(i, 1, QTableWidgetItem(log['ip_address']))
        self.logs_table.setItem(i, 2, QTableWidgetItem(str(log['request'])))
        self.logs_table.setItem(i, 3, QTableWidgetItem(str(log['response'])))
        self.logs_table.setItem(i, 4, QTableWidgetItem(str(log['timestamp'])))

def add_user(self):
    dialog = AddUserDialog(self)
    if dialog.exec_():
        username = dialog.username_input.text()
        password = dialog.password_input.text()
        role = dialog.role_combo.currentText()

        api_key = create_user(username, password, role)
        self.load_users()

def delete_user(self, username):
    users_collection.delete_one({"username": username})
    self.load_users()
class AddUserDialog(QDialog): def init(self, parent=None): super().init(parent) self.setWindowTitle("Add User") layout = QFormLayout() self.setLayout(layout)

    self.username_input = QLineEdit()
    self.password_input = QLineEdit()
    self.password_input.setEchoMode(QLineEdit.Password)
    self.role_combo = QComboBox()
    self.role_combo.addItems(["basic", "premium", "vip"])

    layout.addRow("Username:", self.username_input)
    layout.addRow("Password:", self.password_input)
    layout.addRow("Role:", self.role_combo)

    buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
    buttons.accepted.connect(self.accept)
    buttons.rejected.connect(self.reject)
    layout.addRow(buttons)
if name == "main": app = QApplication(sys.argv) admin_panel = AdminPanel() admin_panel.show() sys.exit(app.exec_())


This admin panel provides a basic interface for managing users and viewing logs. You can expand it to include more features like:

1. Editing user roles
2. Adjusting rate limits for roles
3. More detailed log viewing and filtering
4. Real-time monitoring of API usage

To run the admin panel:

1. Make sure you have PyQt5 installed: `pip install PyQt5`
2. Run the admin_panel.py script: `python admin_panel.py`

Remember to integrate the user management, rate limiting, and logging features into your main API code. You'll need to modify your existing endpoints to check for API keys, apply rate limits, and log requests and responses.
where the admin panel will be able to make keys, add users, delete users, and all the other features, users requests will reset at 12 am utc  every day
---FEATURE-DESCRIPTION-END---

In your response, output only the new updated project specification, without any additional messages to the user.
If there is no feature description just output the original project specification.