7.8 KiB
Architecture and Folder Structure
Overview
The application is built using the FastAPI framework for the backend, allowing it to handle HTTP requests efficiently. The application interacts with external APIs, including an LLM (Large Language Model) service for generating creative prompts and a WebSocket-based service for generating images based on user input.
The modular architecture breaks the code into components based on functionality, which promotes separation of concerns, improves maintainability, and scales better as the application grows.
Folder Structure
├── backend/
│ ├── __init__.py
│ ├── main.py # Entry point for FastAPI and application startup
│ ├── routes.py # Contains all the route definitions for the API
│ ├── services.py # Contains the business logic, interactions with external services like LLMs, image generation, etc.
│ ├── models.py # Defines Pydantic models like LLMRequest, PromptRequest, etc.
├── Dockerfile
├── .dockerignore
├── .env
├── .gitignore
├── Readme.md
├── requirements.txt
├── workflow.json
├── quick_prompts.json
├── ui/
│ ├── index.html
│ ├── script.js
│ ├── style.css
Why This Folder Structure?
-
Separation of Concerns: By splitting the application into routes, services, and models, each file has a focused responsibility:
routes.pyhandles request routing and endpoint definitions.services.pyhandles the core business logic, interactions with external services, and complex operations.models.pycontains the data models that define the request and response formats.
-
Scalability: As the application grows, this structure allows easy addition of new features without bloating any single file. For instance, adding new routes can be done by simply extending
routes.pywithout touching the core logic inservices.py. -
Reusability: The modular approach allows the business logic in
services.pyto be reused in different routes or even across multiple applications without needing to rewrite the code. -
Maintainability: When each file has a single responsibility, debugging and extending the application become much easier. If a bug occurs in the prompt generation, we know that we only need to investigate
services.py. Similarly, changes to how data is structured are confined tomodels.py.
Flow of the Application
-
Application Entry Point (
main.py)- The FastAPI application is initialized in
main.py. It serves as the entry point for the entire backend, mounting theroutes.pyvia theinclude_router()function. - Static files (like HTML, CSS, and JS) are mounted to serve the front-end resources.
app.mount("/static", StaticFiles(directory="ui"), name="static") - The FastAPI application is initialized in
-
Routing and Request Handling (
routes.py)-
The
routes.pyfile defines all the HTTP routes, such as:GET /: Serves the main HTML file (index.html).GET /quick_prompts/: Serves predefined prompts from thequick_prompts.jsonfile.POST /ask_llm/: Handles requests to the LLM to generate creative ideas based on user input.POST /generate_images/: Triggers the image generation process.
-
The routes define what action should be taken when the FastAPI server receives an HTTP request, but the core business logic is abstracted into
services.py.
-
-
Business Logic and External Services (
services.py)-
Core Logic: This file handles the interactions with external services (LLM, WebSocket server) and encapsulates the complex operations.
-
Why separate business logic?: The logic of sending requests to the LLM, generating images, and fetching results from WebSocket is often intricate and should not be mixed with routing. By isolating this functionality in
services.py, the code becomes more maintainable and reusable. -
LLM Interaction:
- The
ask_llm_servicefunction interacts with the Ollama server to send a prompt and retrieve a creative response. This is done by making a POST request to the LLM service's API and processing the response. - The separation of this logic means you can easily change the external LLM service in the future without altering the core routing code.
response = requests.post(ollama_server_url, json=ollama_request, headers={"Content-Type": "application/json"}) response_data = response.json() - The
-
Image Generation:
- The
generate_imagesfunction handles image generation. It connects to a WebSocket server, sends a prompt, and processes the responses, eventually returning the images in a suitable format. - Again, this complex operation is encapsulated in
services.py, separating the logic from the routing layer.
- The
-
-
Data Models and Validation (
models.py)-
This file defines the structure of the data used in the application, using Pydantic models to enforce strict validation of input.
-
Why Pydantic models?: They allow for automatic validation of request bodies, making the application more robust by rejecting invalid input before it even reaches the business logic.
-
Models:
LLMRequest: Defines the schema for a request to the LLM service, ensuring that apositive_promptis provided.PromptRequest: Defines the schema for the image generation request, ensuring thatpositive_prompt,negative_prompt, and other parameters (e.g., image resolution) are valid.
class LLMRequest(BaseModel): positive_prompt: str class PromptRequest(BaseModel): positive_prompt: str negative_prompt: str steps: int = 25 width: int = 512 height: int = 512
-
Request-Response Flow
1. Frontend Request
- A request comes from the front-end (served from
ui/index.html). - Examples:
- When the user submits a text prompt for LLM:
POST /ask_llm/ - When the user requests image generation:
POST /generate_images/
- When the user submits a text prompt for LLM:
2. Routing Layer (routes.py)
- The incoming HTTP request is routed by FastAPI, which directs it to the appropriate endpoint handler in
routes.py. - Example: A request to
POST /ask_llm/is handled by theask_llmfunction, which parses the request data and then calls the corresponding function inservices.py.
3. Business Logic Layer (services.py)
- The service layer handles the core operations:
- If the request involves an LLM,
ask_llm_service()sends a POST request to the LLM API. - If the request is for image generation,
generate_images()opens a WebSocket connection, interacts with the image generation service, and processes the result.
- If the request involves an LLM,
4. Response Generation
- After processing in the service layer, the results (e.g., LLM response, images) are returned to
routes.py. - The route function wraps the results in a suitable response object (e.g.,
JSONResponse,StreamingResponse) and returns it to the frontend. - The front-end then updates based on the server's response.
This architecture and folder structure prioritize clarity, scalability, and maintainability. By separating the application into layers, each responsible for a single aspect of the program's functionality, we can easily add new features, modify existing functionality, and debug any issues.
- Routes handle request dispatching and control the API's behavior.
- Services handle all the business logic and complex operations.
- Models ensure data consistency and validation, leading to fewer errors.
This modular structure also enables easier testing and deployment since each module can be tested individually without breaking the overall flow of the application.