DreamCanvas setup : to be reviewed
This commit is contained in:
7
On host/DreamCanvas/.dockerignore
Normal file
7
On host/DreamCanvas/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.db
|
||||
*.log
|
||||
outputs/
|
||||
3
On host/DreamCanvas/.env
Normal file
3
On host/DreamCanvas/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
# COMFYUI_SERVER_ADDRESS=192.168.1.10:8188
|
||||
# OLLAMA_SERVER_ADDRESS=192.168.1.10:11436
|
||||
# OLLAMA_MODEL=hermes3:70b
|
||||
50
On host/DreamCanvas/.gitignore
vendored
Normal file
50
On host/DreamCanvas/.gitignore
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# Compiled Python files
|
||||
*.pyc
|
||||
__pycache__/
|
||||
|
||||
# IPython notebook checkpoints
|
||||
*.ipynb_checkpoints
|
||||
|
||||
# Virtual environment related files
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
.env/
|
||||
|
||||
# IDE configuration files (if you use a specific one like VSCode, PyCharm, etc.)
|
||||
.idea/
|
||||
*.iml
|
||||
*.pydevproject
|
||||
|
||||
# Debug and temporary files
|
||||
*.log
|
||||
.debug/
|
||||
*~
|
||||
\#*\#
|
||||
*_old*
|
||||
*_backup*
|
||||
|
||||
# Package management files (like Pipfile, poetry)
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
poetry.lock
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# Distribution related files
|
||||
setup.py
|
||||
pyproject.toml
|
||||
|
||||
# Third party dependencies (like node_modules, vendor)
|
||||
node_modules/
|
||||
vendor/
|
||||
|
||||
# Files or directories to ignore in all repositories
|
||||
**/Thumbs.db
|
||||
**/.DS_Store
|
||||
# **/.env
|
||||
|
||||
outputs/
|
||||
21
On host/DreamCanvas/Dockerfile
Normal file
21
On host/DreamCanvas/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
# Step 1: Use an official Python runtime as a base image
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Step 2: Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Step 3: Copy the current directory contents into the container at /app
|
||||
COPY . /app
|
||||
|
||||
# Step 4: Install any required packages specified in requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Step 5: Expose the port FastAPI will run on
|
||||
EXPOSE 8000
|
||||
|
||||
# Step 6: Set environment variables (Optional, if not using .env)
|
||||
# ENV COMFYUI_SERVER_ADDRESS=192.168.1.10:8188
|
||||
# ENV OLLAMA_SERVER_ADDRESS=192.168.1.10:11436
|
||||
|
||||
# Step 7: Run FastAPI using Uvicorn with the correct path for the entry point
|
||||
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
269
On host/DreamCanvas/Readme.md
Normal file
269
On host/DreamCanvas/Readme.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# **DreamCanvas - AI-Powered Creativity**
|
||||
|
||||
DreamCanvas is an AI-powered image generator that allows users to create high-quality, creative images using ComfyUI and integrates with a local Large Language Model (LLM) via Ollama. This project includes a FastAPI backend, a dynamic web interface, and support for user-configurable models and servers.
|
||||
|
||||
---
|
||||
|
||||
## **Table of Contents**
|
||||
- [Setup](#setup)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [Running the Server](#running-the-server)
|
||||
- [Running with Docker](#running-with-docker)
|
||||
- [Functionality](#functionality)
|
||||
- [Positive and Negative Prompts](#positive-and-negative-prompts)
|
||||
- [LLM-Assisted Prompt Generation](#llm-assisted-prompt-generation)
|
||||
- [Quick Prompts](#quick-prompts)
|
||||
- [Image Caching and Navigation](#image-caching-and-navigation)
|
||||
- [UI Reset](#ui-reset)
|
||||
- [Architecture](#architecture)
|
||||
- [Backend](#backend)
|
||||
- [Key Endpoints](#key-endpoints)
|
||||
- [Frontend](#frontend)
|
||||
- [UI Components](#ui-components)
|
||||
- [Tools and Libraries](#tools-and-libraries)
|
||||
- [Testing](#testing)
|
||||
|
||||
---
|
||||
|
||||
## **Setup**
|
||||
|
||||
### **Requirements**
|
||||
|
||||
1. **Conda Environment**:
|
||||
- The project uses Conda for environment management. Make sure Conda is installed on your system.
|
||||
|
||||
2. **ComfyUI**:
|
||||
- ComfyUI should be installed and running. You must have the checkpoint `realvisxlV50Lightning.Ng9I.safetensors` installed in the `checkpoints` folder for the workflow.
|
||||
- Alternatively, you can modify `workflow.json` to use any other model/checkpoint.
|
||||
|
||||
3. **Ollama**:
|
||||
- Ollama LLM server should be installed and accessible.
|
||||
|
||||
4. **Configuration via `.env`**:
|
||||
- The project uses a `.env` file for configuring server addresses. Below are custom configuration settings:
|
||||
```bash
|
||||
COMFYUI_SERVER_ADDRESS=192.168.1.10:8188
|
||||
OLLAMA_SERVER_ADDRESS=192.168.1.10:11436
|
||||
```
|
||||
- Adjust these values to match your environment.
|
||||
|
||||
### **Installation**
|
||||
|
||||
1. **Clone the Repository**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Teachings/DreamCanvas.git
|
||||
cd DreamCanvas
|
||||
```
|
||||
|
||||
2. **Set Up Conda Environment**:
|
||||
|
||||
Create and activate the Conda environment:
|
||||
|
||||
```bash
|
||||
conda create --name dreamcanvas python=3.12
|
||||
conda activate dreamcanvas
|
||||
```
|
||||
|
||||
3. **Install Dependencies**:
|
||||
|
||||
Install the project dependencies listed in `requirements.txt`:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. **Set Up `.env` File**:
|
||||
|
||||
Ensure the `.env` file exists in the project root and contains the correct server addresses for ComfyUI and Ollama.
|
||||
|
||||
```bash
|
||||
COMFYUI_SERVER_ADDRESS=192.168.1.10:8188
|
||||
OLLAMA_SERVER_ADDRESS=192.168.1.10:11436
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Running the Server**
|
||||
|
||||
### **Local Environment**
|
||||
|
||||
To run the FastAPI server in your local environment, use the following command:
|
||||
|
||||
```bash
|
||||
uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
This will start the app on `http://localhost:8000/`.
|
||||
|
||||
To ensure that ComfyUI is functioning correctly, you can test the connection using the workflow defined in `workflow.json`.
|
||||
|
||||
---
|
||||
|
||||
## **Running with Docker**
|
||||
|
||||
If you prefer to run the application inside a Docker container, the following steps guide you through building and running the containerized application.
|
||||
|
||||
### **1. Build the Docker Image**
|
||||
|
||||
Navigate to the project directory and build the Docker image:
|
||||
|
||||
```bash
|
||||
docker build -t dreamcanvas .
|
||||
```
|
||||
|
||||
### **2. Run the Docker Container**
|
||||
|
||||
Once the Docker image is built, run the container:
|
||||
|
||||
```bash
|
||||
docker run -d -p 8000:8000 --env-file .env --name dreamcanvas dreamcanvas
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Start the container in **detached mode** (`-d`).
|
||||
- Map port **8000** of the container to port **8000** on your host.
|
||||
- Use the `.env` file to set environment variables.
|
||||
|
||||
### **3. Access the Application**
|
||||
|
||||
You can now access the application at `http://localhost:8000/`
|
||||
|
||||
---
|
||||
|
||||
## **Functionality**
|
||||
|
||||
### **Positive and Negative Prompts**
|
||||
|
||||
- **Positive Prompt**: Specifies the elements to include in the generated image (e.g., "4k, highly detailed, hyperrealistic").
|
||||
- **Negative Prompt**: Specifies elements to avoid in the image (e.g., "blurry, watermark").
|
||||
|
||||
### **LLM-Assisted Prompt Generation**
|
||||
|
||||
- **Ask LLM for a Creative Idea**: The user can request a creative prompt suggestion from a locally hosted LLM (Ollama). The generated prompt can be applied to the positive prompt field.
|
||||
|
||||
### **Quick Prompts**
|
||||
|
||||
- **Preconfigured Prompts**: Both positive and negative quick prompts are available via buttons. Clicking a button auto-fills the corresponding input field.
|
||||
- **Custom Prompts**: Themed prompts (like **Halloween** or **Christmas**) are dynamically loaded from the `quick_prompts.json` file. Adding new themes is as easy as editing this file.
|
||||
|
||||
### **Image Caching and Navigation**
|
||||
|
||||
- **Image History**: The app caches generated images within the session. Users can navigate through cached images using the **Previous** and **Next** buttons.
|
||||
- **Cache Clearing**: Cached images are cleared when the browser is refreshed or when the **Reset** button is clicked.
|
||||
|
||||
### **UI Reset**
|
||||
|
||||
- The **Reset** button clears all input fields, resets generated images, and clears the image cache.
|
||||
|
||||
---
|
||||
|
||||
## **Architecture**
|
||||
|
||||
### **Backend**
|
||||
|
||||
The backend is powered by **FastAPI** and handles the following operations:
|
||||
- Generating images using ComfyUI.
|
||||
- Fetching creative suggestions from the local LLM.
|
||||
- Serving quick prompts from configuration files.
|
||||
|
||||
#### **Key Endpoints**
|
||||
|
||||
1. **POST /generate_images/**
|
||||
- **Description**: Generates an AI image using the provided prompts and image settings.
|
||||
- **Request Example**:
|
||||
```json
|
||||
{
|
||||
"positive_prompt": "a beautiful sunset",
|
||||
"negative_prompt": "blurry",
|
||||
"steps": 25,
|
||||
"width": 512,
|
||||
"height": 512
|
||||
}
|
||||
```
|
||||
- **Response**: A binary stream containing the generated image.
|
||||
|
||||
2. **POST /ask_llm/**
|
||||
- **Description**: Requests a creative prompt from the local LLM server (Ollama).
|
||||
- **Request Example**:
|
||||
```json
|
||||
{
|
||||
"positive_prompt": "a beautiful sunset"
|
||||
}
|
||||
```
|
||||
- **Response**:
|
||||
```json
|
||||
{
|
||||
"assistant_reply": "How about a stunning sunset over the mountains with golden light reflecting on the water?"
|
||||
}
|
||||
```
|
||||
|
||||
3. **GET /quick_prompts/**
|
||||
- **Description**: Retrieves quick prompt configurations from the `quick_prompts.json` file for dynamic UI updates.
|
||||
- **Response Example**:
|
||||
```json
|
||||
{
|
||||
"Positive Quick Prompts": [
|
||||
{ "label": "4K", "value": "4K" },
|
||||
{ "label": "Highly Detailed", "value": "highly detailed" }
|
||||
],
|
||||
"Negative Quick Prompts": [
|
||||
{ "label": "Blurry", "value": "blurry" },
|
||||
{ "label": "Watermark", "value": "watermark" }
|
||||
],
|
||||
"Halloween": [
|
||||
{ "label": "Black Background", "value": "black background" },
|
||||
{ "label": "Witch", "value": "witch" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### **Frontend**
|
||||
|
||||
The frontend is built with HTML, CSS, and JavaScript. It dynamically interacts with the backend for:
|
||||
- Generating images.
|
||||
- Fetching creative prompts from the LLM.
|
||||
- Loading quick prompt configurations from `quick_prompts.json`.
|
||||
|
||||
#### **UI Components**
|
||||
|
||||
1. **Image Generation Form**:
|
||||
- Includes fields for positive and negative prompts, image steps, width, and height.
|
||||
- Quick prompt buttons for easy input.
|
||||
|
||||
2. **LLM Integration**:
|
||||
- A section that allows users to request and apply creative prompts generated by the LLM.
|
||||
|
||||
3. **Image Display and Navigation**:
|
||||
- Displays the generated images and includes buttons for navigating through cached images.
|
||||
|
||||
4. **Reset Functionality**:
|
||||
- A **Reset** button to clear all input fields and generated image history.
|
||||
|
||||
### **Tools and Libraries**
|
||||
|
||||
1. **FastAPI**: Web framework for building the backend.
|
||||
2. **Uvicorn**: ASGI server used to run the FastAPI application.
|
||||
3. **Ollama**: Locally hosted LLM for generating creative prompts.
|
||||
4. **Pillow**: Python Imaging Library used to handle image operations.
|
||||
5. **Bootstrap**: CSS framework for styling the UI.
|
||||
6. **JavaScript (Fetch API)**: Handles asynchronous requests to the backend.
|
||||
|
||||
---
|
||||
|
||||
## **Testing**
|
||||
|
||||
You can test the ComfyUI workflow by running the FastAPI server as described above. Use the `/generate_images/` endpoint to generate images and validate that the workflow is functioning correctly.
|
||||
|
||||
To test the LLM integration, use the `/ask_llm/` endpoint to request a creative prompt from the locally hosted Ollama LLM.
|
||||
|
||||
For Docker-based setups, ensure that the `.env` file is correctly set up with the server addresses and run the container as described in the [Running with Docker](#running-with-docker) section.
|
||||
|
||||
## **Kill Server**
|
||||
|
||||
If you need to force kill the server process, you can use the following command:
|
||||
|
||||
```bash
|
||||
sudo kill -9 $(sudo lsof -t -i :8000)
|
||||
```
|
||||
0
On host/DreamCanvas/backend/__init__.py
Normal file
0
On host/DreamCanvas/backend/__init__.py
Normal file
15
On host/DreamCanvas/backend/main.py
Normal file
15
On host/DreamCanvas/backend/main.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from .routes import router as api_router
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Serve static files (CSS, JS)
|
||||
app.mount("/static", StaticFiles(directory="ui"), name="static")
|
||||
|
||||
# Include all API routes
|
||||
app.include_router(api_router)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
13
On host/DreamCanvas/backend/models.py
Normal file
13
On host/DreamCanvas/backend/models.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Request model for LLM
|
||||
class LLMRequest(BaseModel):
|
||||
positive_prompt: str
|
||||
|
||||
# Request model for image generation
|
||||
class PromptRequest(BaseModel):
|
||||
positive_prompt: str
|
||||
negative_prompt: str
|
||||
steps: int = 25
|
||||
width: int = 512
|
||||
height: int = 512
|
||||
58
On host/DreamCanvas/backend/routes.py
Normal file
58
On host/DreamCanvas/backend/routes.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||
from .models import LLMRequest, PromptRequest
|
||||
from .services import generate_images, get_quick_prompts_data, ask_llm_service
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Serve HTML file (index.html)
|
||||
@router.get("/")
|
||||
async def get_index():
|
||||
return FileResponse("ui/index.html")
|
||||
|
||||
# Endpoint to serve quick prompts from configuration file
|
||||
@router.get("/quick_prompts/")
|
||||
async def get_quick_prompts():
|
||||
try:
|
||||
prompts = get_quick_prompts_data()
|
||||
return JSONResponse(content=prompts)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="Error reading quick prompts file.")
|
||||
|
||||
# Endpoint to ask the LLM for creative ideas based on the positive prompt
|
||||
@router.post("/ask_llm/")
|
||||
async def ask_llm(request: LLMRequest):
|
||||
try:
|
||||
response = ask_llm_service(request.positive_prompt)
|
||||
return JSONResponse(content={"assistant_reply": response})
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="Error interacting with LLM: " + str(e))
|
||||
|
||||
# Endpoint to generate images
|
||||
@router.post("/generate_images/")
|
||||
async def generate_images_api(request: PromptRequest):
|
||||
try:
|
||||
images, seed = await generate_images(
|
||||
request.positive_prompt,
|
||||
request.negative_prompt,
|
||||
request.steps,
|
||||
(request.width, request.height)
|
||||
)
|
||||
|
||||
# Convert images to a format FastAPI can return
|
||||
image_responses = []
|
||||
for node_id in images:
|
||||
for image_data in images[node_id]:
|
||||
img = Image.open(io.BytesIO(image_data))
|
||||
img_byte_arr = io.BytesIO()
|
||||
img.save(img_byte_arr, format='PNG')
|
||||
img_byte_arr.seek(0)
|
||||
|
||||
# Append the image response to a list
|
||||
image_responses.append(StreamingResponse(img_byte_arr, media_type="image/png"))
|
||||
|
||||
return image_responses[0] # Return the first image for simplicity
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Image generation failed: {str(e)}")
|
||||
141
On host/DreamCanvas/backend/services.py
Normal file
141
On host/DreamCanvas/backend/services.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import random
|
||||
import uuid
|
||||
import websocket as ws_client
|
||||
from dotenv import load_dotenv
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.parse import urlencode
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
server_address = os.getenv('COMFYUI_SERVER_ADDRESS', 'localhost:8188')
|
||||
client_id = str(uuid.uuid4())
|
||||
ollama_server_address = os.getenv('OLLAMA_SERVER_ADDRESS', 'localhost:11434')
|
||||
ollama_server_url = f"http://{ollama_server_address}/v1/chat/completions"
|
||||
ollama_model = os.getenv('OLLAMA_MODEL', 'llama3.1:latest') # Load model from .env
|
||||
system_prompt = """
|
||||
You are an AI model that transforms user input into concise, creative, and visually descriptive prompts for AI image generation. If the user input is empty, generate a creative, detailed description automatically. Always respond with a single, visually descriptive line, incorporating elements such as hyperrealistic, 4K, and detailed imagery when applicable.
|
||||
|
||||
Guidelines:
|
||||
1. **Be Specific and Clear**: Ensure the response describes the subject, setting, and style in a visually engaging manner.
|
||||
2. **Keep It Brief**: Limit responses to one or two sentences that provide a vivid image while remaining concise.
|
||||
3. **Encourage Creativity**: Incorporate artistic styles like hyperrealism, 4K resolution, cinematic lighting, or surrealism where appropriate.
|
||||
4. **Handle Empty Input**: If the input is empty, create an imaginative and detailed prompt using the aforementioned elements.
|
||||
5. **No Extra Communication**: Respond only with the transformed prompt without additional commentary.
|
||||
|
||||
### Example Inputs and Outputs:
|
||||
|
||||
**Input**: "A futuristic city at night with neon lights and flying cars."
|
||||
**Output**: "A neon-lit futuristic cityscape at night, with flying cars darting between towering skyscrapers, glowing in vibrant 4K detail."
|
||||
|
||||
**Input**: "A serene beach with palm trees and a sunset."
|
||||
**Output**: "A serene beach at sunset, palm trees swaying gently as the golden light reflects off the 4K ocean waves."
|
||||
|
||||
**Input**: "A magical forest with glowing trees and animals."
|
||||
**Output**: "A hyperrealistic forest where glowing trees light the way for mystical creatures under a twilight sky, captured in stunning 4K."
|
||||
|
||||
**Input**: ""
|
||||
**Output**: "An intricately detailed 4K portrait of a warrior in golden armor, standing proudly under the dramatic glow of cinematic lighting."
|
||||
|
||||
**Input**: "A watercolor painting of a rainy street."
|
||||
**Output**: "A dreamy watercolor painting of a rainy street, soft reflections glistening in the puddles under warm streetlights."
|
||||
"""
|
||||
# Service to get quick prompts data
|
||||
def get_quick_prompts_data():
|
||||
with open("quick_prompts.json", "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
# Service to ask LLM for response
|
||||
def ask_llm_service(positive_prompt: str):
|
||||
ollama_request = {
|
||||
"model": ollama_model, # Use model from .env
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_prompt
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": positive_prompt
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response = requests.post(ollama_server_url, json=ollama_request, headers={"Content-Type": "application/json"})
|
||||
response_data = response.json()
|
||||
return response_data["choices"][0]["message"]["content"]
|
||||
|
||||
# Service to queue a prompt
|
||||
def queue_prompt(prompt):
|
||||
p = {"prompt": prompt, "client_id": client_id}
|
||||
data = json.dumps(p).encode('utf-8')
|
||||
req = Request(f"http://{server_address}/prompt", data=data)
|
||||
return json.loads(urlopen(req).read())
|
||||
|
||||
# Service to get image
|
||||
def get_image(filename, subfolder, folder_type):
|
||||
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
|
||||
url_values = urlencode(data)
|
||||
with urlopen(f"http://{server_address}/view?{url_values}") as response:
|
||||
return response.read()
|
||||
|
||||
# Service to get history
|
||||
def get_history(prompt_id):
|
||||
with urlopen(f"http://{server_address}/history/{prompt_id}") as response:
|
||||
return json.loads(response.read())
|
||||
|
||||
# WebSocket image generation service
|
||||
async def get_images(ws, prompt):
|
||||
prompt_id = queue_prompt(prompt)['prompt_id']
|
||||
output_images = {}
|
||||
|
||||
while True:
|
||||
out = ws.recv()
|
||||
if isinstance(out, str):
|
||||
message = json.loads(out)
|
||||
if message['type'] == 'executing':
|
||||
data = message['data']
|
||||
if data['node'] is None and data['prompt_id'] == prompt_id:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
|
||||
history = get_history(prompt_id)[prompt_id]
|
||||
for o in history['outputs']:
|
||||
for node_id in history['outputs']:
|
||||
node_output = history['outputs'][node_id]
|
||||
if 'images' in node_output:
|
||||
images_output = []
|
||||
for image in node_output['images']:
|
||||
image_data = get_image(image['filename'], image['subfolder'], image['type'])
|
||||
images_output.append(image_data)
|
||||
output_images[node_id] = images_output
|
||||
|
||||
return output_images
|
||||
|
||||
# Main image generation function
|
||||
async def generate_images(positive_prompt, negative_prompt, steps=25, resolution=(512, 512)):
|
||||
ws = ws_client.WebSocket()
|
||||
ws.connect(f"ws://{server_address}/ws?clientId={client_id}")
|
||||
|
||||
with open("workflow.json", "r", encoding="utf-8") as f:
|
||||
workflow_data = f.read()
|
||||
|
||||
workflow = json.loads(workflow_data)
|
||||
|
||||
workflow["6"]["inputs"]["text"] = positive_prompt
|
||||
workflow["7"]["inputs"]["text"] = negative_prompt
|
||||
workflow["3"]["inputs"]["steps"] = steps
|
||||
workflow["5"]["inputs"]["width"] = resolution[0]
|
||||
workflow["5"]["inputs"]["height"] = resolution[1]
|
||||
seed = random.randint(1, 1000000000)
|
||||
workflow["3"]["inputs"]["seed"] = seed
|
||||
|
||||
images = await get_images(ws, workflow)
|
||||
|
||||
ws.close()
|
||||
|
||||
return images, seed
|
||||
138
On host/DreamCanvas/docs/BackendArchitecture.md
Normal file
138
On host/DreamCanvas/docs/BackendArchitecture.md
Normal file
@@ -0,0 +1,138 @@
|
||||
## **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**
|
||||
|
||||
```bash
|
||||
├── 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.py` handles request routing and endpoint definitions.
|
||||
- `services.py` handles the core business logic, interactions with external services, and complex operations.
|
||||
- `models.py` contains 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.py` without touching the core logic in `services.py`.
|
||||
|
||||
- **Reusability**: The modular approach allows the business logic in `services.py` to 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 to `models.py`.
|
||||
|
||||
---
|
||||
|
||||
## **Flow of the Application**
|
||||
|
||||
1. **Application Entry Point** (`main.py`)
|
||||
- The **FastAPI** application is initialized in `main.py`. It serves as the entry point for the entire backend, mounting the `routes.py` via the `include_router()` function.
|
||||
- Static files (like HTML, CSS, and JS) are mounted to serve the front-end resources.
|
||||
|
||||
```python
|
||||
app.mount("/static", StaticFiles(directory="ui"), name="static")
|
||||
```
|
||||
|
||||
2. **Routing and Request Handling** (`routes.py`)
|
||||
- The `routes.py` file defines all the HTTP routes, such as:
|
||||
- `GET /`: Serves the main HTML file (`index.html`).
|
||||
- `GET /quick_prompts/`: Serves predefined prompts from the `quick_prompts.json` file.
|
||||
- `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`.
|
||||
|
||||
3. **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_service` function 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.
|
||||
|
||||
```python
|
||||
response = requests.post(ollama_server_url, json=ollama_request, headers={"Content-Type": "application/json"})
|
||||
response_data = response.json()
|
||||
```
|
||||
|
||||
- **Image Generation**:
|
||||
- The `generate_images` function 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.
|
||||
|
||||
4. **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 a `positive_prompt` is provided.
|
||||
- `PromptRequest`: Defines the schema for the image generation request, ensuring that `positive_prompt`, `negative_prompt`, and other parameters (e.g., image resolution) are valid.
|
||||
|
||||
```python
|
||||
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/`
|
||||
|
||||
### 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 the `ask_llm` function, which parses the request data and then calls the corresponding function in `services.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.
|
||||
|
||||
### 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.
|
||||
|
||||
109
On host/DreamCanvas/docs/BuildAndDeploy.md
Normal file
109
On host/DreamCanvas/docs/BuildAndDeploy.md
Normal file
@@ -0,0 +1,109 @@
|
||||
## **Running the Application with and without Docker**
|
||||
|
||||
This guide explains how to run the application both locally and via Docker, using the command `docker run -d -p 8000:8000 --name dreamcanvas dreamcanvas`. It also covers how to test the application via a web browser and cURL.
|
||||
|
||||
---
|
||||
|
||||
### **Running the Application Locally**
|
||||
|
||||
1. **Install Dependencies**:
|
||||
Ensure you have Python and all required dependencies installed. In the root of your project directory, run:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Start the Application**:
|
||||
Run the following command to start the FastAPI server using Uvicorn:
|
||||
```bash
|
||||
uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
3. **Test the Application via Browser**:
|
||||
Open a web browser and navigate to `http://localhost:8000/docs`. This will bring up **Swagger UI**, where you can interact with the API endpoints.
|
||||
|
||||
4. **Test the Application via cURL**:
|
||||
To test the `/generate_images/` endpoint, run this **cURL** command:
|
||||
```bash
|
||||
curl 'http://localhost:8000/generate_images/' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"positive_prompt": "Pretty woman in her late 20s 4k highly detailed hyperrealistic",
|
||||
"negative_prompt": "Trees low quality blurry watermark",
|
||||
"steps": 25,
|
||||
"width": 512,
|
||||
"height": 512
|
||||
}' \
|
||||
--insecure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Running the Application with Docker**
|
||||
|
||||
You can also run the application inside a Docker container, which ensures a consistent environment for deployment and testing.
|
||||
|
||||
#### **1. Build the Docker Image**
|
||||
First, ensure you have Docker installed. Navigate to your project root directory (where the `Dockerfile` is located), and run the following command to build the Docker image:
|
||||
|
||||
```bash
|
||||
docker build -t dreamcanvas .
|
||||
```
|
||||
|
||||
This command will create a Docker image named `dreamcanvas`.
|
||||
|
||||
#### **2. Run the Docker Container**
|
||||
Once the image is built, you can start the application in detached mode using this command:
|
||||
|
||||
```bash
|
||||
docker run -d -p 8000:8000 --name dreamcanvas dreamcanvas
|
||||
```
|
||||
|
||||
- `-d` runs the container in detached mode (in the background).
|
||||
- `-p 8000:8000` maps port 8000 on your host machine to port 8000 inside the Docker container, making the application accessible via `http://localhost:8000`.
|
||||
- `--name dreamcanvas` assigns a name to the running container, making it easier to manage.
|
||||
|
||||
#### **3. Test the Application in Browser**
|
||||
Open your web browser and navigate to `http://localhost:8000/docs` to access **Swagger UI**. This interface allows you to interact with all the available API endpoints.
|
||||
|
||||
#### **4. Test the Application via cURL**
|
||||
To test the `/generate_images/` endpoint, use this **cURL** command:
|
||||
|
||||
```bash
|
||||
curl 'http://localhost:8000/generate_images/' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"positive_prompt": "Pretty woman in her late 20s 4k highly detailed hyperrealistic",
|
||||
"negative_prompt": "Trees low quality blurry watermark",
|
||||
"steps": 25,
|
||||
"width": 512,
|
||||
"height": 512
|
||||
}' \
|
||||
--insecure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Stopping and Managing the Docker Container**
|
||||
|
||||
#### **Stop the Container**
|
||||
If you need to stop the container, you can run:
|
||||
|
||||
```bash
|
||||
docker stop dreamcanvas
|
||||
```
|
||||
|
||||
#### **Restart the Container**
|
||||
To restart the stopped container, use:
|
||||
|
||||
```bash
|
||||
docker start dreamcanvas
|
||||
```
|
||||
|
||||
#### **Remove the Container**
|
||||
To remove the container when you're done testing, run:
|
||||
|
||||
```bash
|
||||
docker rm -f dreamcanvas
|
||||
```
|
||||
|
||||
This will stop and remove the container completely.
|
||||
153
On host/DreamCanvas/docs/ComfyUISetupGuide/ComfyUISetup.md
Normal file
153
On host/DreamCanvas/docs/ComfyUISetupGuide/ComfyUISetup.md
Normal file
@@ -0,0 +1,153 @@
|
||||
|
||||
# ComfyUI Docker Setup with GGUF Support and ComfyUI Manager
|
||||
|
||||
This guide provides detailed steps to build and run **ComfyUI** with **GGUF support** and **ComfyUI Manager** using Docker. The GGUF format is optimized for quantized models, and ComfyUI Manager is included for easy node management.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have the following installed on your system:
|
||||
|
||||
- **Docker**
|
||||
- **NVIDIA GPU with CUDA support** (if using GPU acceleration)
|
||||
|
||||
### 1. Clone the ComfyUI Repository
|
||||
|
||||
First, clone the ComfyUI repository to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/comfyanonymous/ComfyUI.git
|
||||
cd ComfyUI
|
||||
```
|
||||
|
||||
### 2. Create the Dockerfile
|
||||
|
||||
Create a `Dockerfile` in the root of your ComfyUI directory with the following content:
|
||||
|
||||
```Dockerfile
|
||||
# Base image with Python 3.11 and CUDA 12.5 support
|
||||
FROM nvidia/cuda:12.5.0-runtime-ubuntu22.04
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
python3-pip \
|
||||
libgl1-mesa-glx \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the cloned ComfyUI repository
|
||||
COPY . /app
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Clone and install ComfyUI Manager
|
||||
RUN git clone https://github.com/ltdrdata/ComfyUI-Manager.git /app/custom_nodes/ComfyUI-Manager && \
|
||||
pip install -r /app/custom_nodes/ComfyUI-Manager/requirements.txt
|
||||
|
||||
# Clone and install GGUF support for ComfyUI
|
||||
RUN git clone https://github.com/city96/ComfyUI-GGUF.git /app/custom_nodes/ComfyUI-GGUF && \
|
||||
pip install --upgrade gguf
|
||||
|
||||
# Expose the port used by ComfyUI
|
||||
EXPOSE 8188
|
||||
|
||||
# Run ComfyUI with the server binding to 0.0.0.0
|
||||
CMD ["python3", "main.py", "--listen", "0.0.0.0"]
|
||||
```
|
||||
|
||||
### 3. Build the Docker Image
|
||||
|
||||
Navigate to the directory where the `Dockerfile` is located and build the Docker image:
|
||||
|
||||
```bash
|
||||
docker build -t comfyui-gguf:latest .
|
||||
```
|
||||
|
||||
This will create a Docker image named `comfyui-gguf:latest` with both **ComfyUI Manager** and **GGUF support** built in.
|
||||
|
||||
### 4. Run the Docker Container
|
||||
|
||||
Once the image is built, you can run the Docker container with volume mapping for your models.
|
||||
|
||||
```bash
|
||||
docker run --name comfyui -p 8188:8188 --gpus all \
|
||||
-v /home/mukul/dev-ai/vison/models:/app/models \
|
||||
-d comfyui-gguf:latest
|
||||
```
|
||||
|
||||
This command maps your local `models` directory to `/app/models` inside the container and exposes ComfyUI on port `8188`.
|
||||
|
||||
### 5. Download and Place Checkpoint Models
|
||||
|
||||
To use GGUF models or other safetensor models, follow the steps below to download them directly into the `checkpoints` directory.
|
||||
|
||||
1. **Navigate to the Checkpoints Directory**:
|
||||
```bash
|
||||
cd /home/mukul/dev-ai/vison/models/checkpoints
|
||||
```
|
||||
|
||||
2. **Download `flux1-schnell-fp8.safetensors`**:
|
||||
```bash
|
||||
wget https://huggingface.co/Comfy-Org/flux1-schnell/resolve/main/flux1-schnell-fp8.safetensors?download=true -O flux1-schnell-fp8.safetensors
|
||||
```
|
||||
|
||||
3. **Download `flux1-dev-fp8.safetensors`**:
|
||||
```bash
|
||||
wget https://huggingface.co/Comfy-Org/flux1-dev/resolve/main/flux1-dev-fp8.safetensors?download=true -O flux1-dev-fp8.safetensors
|
||||
```
|
||||
|
||||
These commands will place the corresponding `.safetensors` files into the `checkpoints` directory.
|
||||
|
||||
### 6. Access ComfyUI
|
||||
|
||||
After starting the container, access the ComfyUI interface in your web browser:
|
||||
|
||||
```bash
|
||||
http://<your-server-ip>:8188
|
||||
```
|
||||
|
||||
Replace `<your-server-ip>` with your server's IP address or use `localhost` if you're running it locally.
|
||||
|
||||
### 7. Using GGUF Models
|
||||
|
||||
In the ComfyUI interface:
|
||||
- Use the **UnetLoaderGGUF** node (found in the `bootleg` category) to load GGUF models.
|
||||
- Ensure your GGUF files are correctly named and placed in the `/app/models/checkpoints` directory for detection by the loader node.
|
||||
|
||||
### 8. Managing Nodes with ComfyUI Manager
|
||||
|
||||
With **ComfyUI Manager** built into the image:
|
||||
- **Install** missing nodes as needed when uploading workflows.
|
||||
- **Enable/Disable** conflicting nodes from the ComfyUI Manager interface.
|
||||
|
||||
### 9. Stopping and Restarting the Docker Container
|
||||
|
||||
To stop the running container:
|
||||
|
||||
```bash
|
||||
docker stop comfyui
|
||||
```
|
||||
|
||||
To restart the container:
|
||||
|
||||
```bash
|
||||
docker start comfyui
|
||||
```
|
||||
|
||||
### 10. Logs and Troubleshooting
|
||||
|
||||
To view the container logs:
|
||||
|
||||
```bash
|
||||
docker logs comfyui
|
||||
```
|
||||
|
||||
This will provide details if anything goes wrong or if you encounter issues with GGUF models or node management.
|
||||
|
||||
---
|
||||
|
||||
This `README.md` provides the complete steps to set up **ComfyUI with GGUF support** in Docker, with instructions for downloading models into the checkpoints directory and managing nodes using ComfyUI Manager.
|
||||
33
On host/DreamCanvas/docs/ComfyUISetupGuide/Dockerfile
Normal file
33
On host/DreamCanvas/docs/ComfyUISetupGuide/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# Base image with Python 3.11 and CUDA 12.5 support
|
||||
FROM nvidia/cuda:12.5.0-runtime-ubuntu22.04
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
python3-pip \
|
||||
libgl1-mesa-glx \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the cloned ComfyUI repository
|
||||
COPY . /app
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Clone and install ComfyUI Manager
|
||||
RUN git clone https://github.com/ltdrdata/ComfyUI-Manager.git /app/custom_nodes/ComfyUI-Manager && \
|
||||
pip install -r /app/custom_nodes/ComfyUI-Manager/requirements.txt
|
||||
|
||||
# Clone and install GGUF support for ComfyUI
|
||||
RUN git clone https://github.com/city96/ComfyUI-GGUF.git /app/custom_nodes/ComfyUI-GGUF && \
|
||||
pip install --upgrade gguf
|
||||
|
||||
# Expose the port used by ComfyUI
|
||||
EXPOSE 8188
|
||||
|
||||
# Run ComfyUI with the server binding to 0.0.0.0
|
||||
CMD ["python3", "main.py", "--listen", "0.0.0.0"]
|
||||
186
On host/DreamCanvas/docs/FrontendArchitecture.md
Normal file
186
On host/DreamCanvas/docs/FrontendArchitecture.md
Normal file
@@ -0,0 +1,186 @@
|
||||
## JavaScript Methods Documentation for **DreamCanvas**
|
||||
|
||||
### Overview
|
||||
This documentation provides an overview of the key methods in the JavaScript file responsible for handling user interactions, form submissions, image generation, and dynamic prompt loading. It also explains how these methods are connected to the elements defined in the HTML structure.
|
||||
|
||||
These JavaScript methods drive the interactive functionality of the **DreamCanvas** app, handling form submissions, prompt input, and dynamic image generation. They are closely connected to the corresponding HTML elements, enabling a smooth and responsive user experience.
|
||||
---
|
||||
|
||||
### **1. `startTimer()`**
|
||||
**Description:**
|
||||
Starts a timer that displays the elapsed time since the image generation process started.
|
||||
|
||||
- **How it works:**
|
||||
- Captures the current time when the generation process begins.
|
||||
- Updates the elapsed time display every second.
|
||||
- This is useful to show the user how long the process is taking.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `elapsedTime`: This element is updated by the method to display the current time in the format `(MM:SS)`.
|
||||
|
||||
---
|
||||
|
||||
### **2. `stopTimer()`**
|
||||
**Description:**
|
||||
Stops the timer once the image generation process is complete.
|
||||
|
||||
- **How it works:**
|
||||
- Clears the interval that updates the timer.
|
||||
- Hides the timer display by adding the CSS class `d-none`.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `elapsedTime`: The timer display is hidden after this method is called.
|
||||
|
||||
---
|
||||
|
||||
### **3. `showNotification(message, type)`**
|
||||
**Description:**
|
||||
Displays a notification to the user with a custom message and type (success or error).
|
||||
|
||||
- **Parameters:**
|
||||
- `message`: The message to be displayed in the notification.
|
||||
- `type`: The type of notification (`success` or `danger`), which determines the visual style of the alert.
|
||||
|
||||
- **How it works:**
|
||||
- Sets the content and type of the notification element.
|
||||
- Automatically hides the notification after 3 seconds.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `notification`: This is the element where the notification message is displayed.
|
||||
|
||||
---
|
||||
|
||||
### **4. `resetBtn.addEventListener("click", function () { ... })`**
|
||||
**Description:**
|
||||
Handles the form reset action.
|
||||
|
||||
- **How it works:**
|
||||
- Clears the input fields for prompts.
|
||||
- Hides the image result, prompts, and navigation buttons.
|
||||
- Resets the button text and state.
|
||||
- Resets any cached image history and disables image navigation.
|
||||
- Displays a notification confirming that the form has been reset.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `positivePrompt` & `negativePrompt`: The input fields for prompts are cleared.
|
||||
- `generatedImage`: Hides the generated image.
|
||||
- `promptDisplay`: Hides the display of positive and negative prompts.
|
||||
- `imageNavigation`: Hides the navigation buttons for switching between images.
|
||||
|
||||
---
|
||||
|
||||
### **5. `loadQuickPrompts()`**
|
||||
**Description:**
|
||||
Loads quick prompts from the server and populates the respective buttons dynamically.
|
||||
|
||||
- **How it works:**
|
||||
- Fetches quick prompts (positive, negative, and others) from the server.
|
||||
- Dynamically generates buttons based on the prompt categories.
|
||||
- Each button, when clicked, adds its respective prompt to the corresponding input field.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `quickPromptsContainer`: Contains the buttons for different categories of quick prompts.
|
||||
- `positiveKeywords` & `negativeKeywords`: Containers for dynamically generated prompt buttons.
|
||||
|
||||
---
|
||||
|
||||
### **6. `addPositiveKeyword(button, keyword)`**
|
||||
**Description:**
|
||||
Appends a positive keyword to the `positivePrompt` input field when a quick prompt button is clicked.
|
||||
|
||||
- **How it works:**
|
||||
- Updates the `positivePrompt` input field with the selected keyword.
|
||||
- Disables the button after it has been clicked to prevent multiple additions of the same keyword.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `positivePrompt`: The field where the selected positive prompt keyword is added.
|
||||
|
||||
---
|
||||
|
||||
### **7. `askLLMButton.addEventListener("click", function () { ... })`**
|
||||
**Description:**
|
||||
Handles the action of asking the LLM (Language Learning Model) for a creative idea based on the current positive prompt.
|
||||
|
||||
- **How it works:**
|
||||
- Sends the current positive prompt (or a default one) to the server.
|
||||
- Displays a spinner to indicate processing.
|
||||
- Receives the LLM-generated prompt from the server and updates the `llmResponseTextarea`.
|
||||
- Shows the "Use LLM's Creative Prompt" button to allow the user to apply the prompt.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `positivePrompt`: The input value is sent to the server as part of the request.
|
||||
- `llmResponseTextarea`: Displays the response from the LLM.
|
||||
- `askLLMSpinner`: Shows the spinner while waiting for the response.
|
||||
- `useLLMResponseButton`: Becomes visible once the response is ready.
|
||||
|
||||
---
|
||||
|
||||
### **8. `useLLMResponseButton.addEventListener("click", function () { ... })`**
|
||||
**Description:**
|
||||
Uses the LLM's generated creative prompt as the positive prompt.
|
||||
|
||||
- **How it works:**
|
||||
- Copies the content of the `llmResponseTextarea` into the `positivePrompt` input field.
|
||||
- Displays a notification confirming that the prompt has been applied.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `positivePrompt`: Receives the LLM's response as its new value.
|
||||
- `llmResponseTextarea`: Source of the creative prompt.
|
||||
|
||||
---
|
||||
|
||||
### **9. `form.addEventListener("submit", function (event) { ... })`**
|
||||
**Description:**
|
||||
Handles the image generation process when the form is submitted.
|
||||
|
||||
- **How it works:**
|
||||
- Prevents the default form submission behavior.
|
||||
- Disables the "Generate Image" button and starts the spinner and timer.
|
||||
- Sends the form data (positive prompt, negative prompt, steps, width, height) to the server.
|
||||
- Displays the generated image, updates the image history, and enables navigation buttons for image history.
|
||||
- Resets the button and timer after the process is complete.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `positivePrompt`, `negativePrompt`, `steps`, `width`, `height`: Input fields that send data to the server.
|
||||
- `generatedImage`: Displays the generated image.
|
||||
- `spinner`: Shows a loading spinner during the process.
|
||||
- `buttonText`: Updates the button text to indicate image generation.
|
||||
- `imageNavigation`: Shows the image navigation buttons after the image is generated.
|
||||
|
||||
---
|
||||
|
||||
### **10. `updateImageNavigation()`**
|
||||
**Description:**
|
||||
Updates the state of the previous and next buttons based on the current position in the image history.
|
||||
|
||||
- **How it works:**
|
||||
- Enables/disables the "Previous" and "Next" buttons depending on whether there are previous/next images in the history.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `prevImage` & `nextImage`: Navigation buttons to switch between generated images.
|
||||
- `imageNavigation`: Shows or hides the entire navigation section.
|
||||
|
||||
---
|
||||
|
||||
### **11. `prevImageBtn.addEventListener("click", function () { ... })`**
|
||||
**Description:**
|
||||
Navigates to the previous image in the image history when the "Previous" button is clicked.
|
||||
|
||||
- **How it works:**
|
||||
- Decreases the `currentImageIndex` and updates the `generatedImage` to display the previous image.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `generatedImage`: Displays the previous image.
|
||||
|
||||
---
|
||||
|
||||
### **12. `nextImageBtn.addEventListener("click", function () { ... })`**
|
||||
**Description:**
|
||||
Navigates to the next image in the image history when the "Next" button is clicked.
|
||||
|
||||
- **How it works:**
|
||||
- Increases the `currentImageIndex` and updates the `generatedImage` to display the next image.
|
||||
|
||||
- **Connected HTML Elements:**
|
||||
- `generatedImage`: Displays the next image.
|
||||
|
||||
188
On host/DreamCanvas/main.py
Normal file
188
On host/DreamCanvas/main.py
Normal file
@@ -0,0 +1,188 @@
|
||||
import websocket # websocket-client
|
||||
import uuid
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import random
|
||||
from PIL import Image
|
||||
import io
|
||||
from termcolor import colored
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Step 1: Initialize the connection settings and load environment variables
|
||||
print(colored("Step 1: Initialize the connection settings and load environment variables.", "cyan"))
|
||||
print(colored("Loading configuration from the .env file.", "yellow"))
|
||||
load_dotenv()
|
||||
|
||||
# Get server address from environment variable, default to "localhost:8188" if not set
|
||||
server_address = os.getenv('COMFYUI_SERVER_ADDRESS', 'localhost:8188')
|
||||
client_id = str(uuid.uuid4())
|
||||
|
||||
# Display the server address and client ID for transparency
|
||||
print(colored(f"Server Address: {server_address}", "magenta"))
|
||||
print(colored(f"Generated Client ID: {client_id}", "magenta"))
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
# Queue prompt function
|
||||
def queue_prompt(prompt):
|
||||
p = {"prompt": prompt, "client_id": client_id}
|
||||
data = json.dumps(p, indent=4).encode('utf-8') # Prettify JSON for print
|
||||
req = urllib.request.Request(f"http://{server_address}/prompt", data=data)
|
||||
|
||||
# Step 5: Queue the prompt and prepare to send it to the ComfyUI server
|
||||
print(colored(f"Step 5: Queuing the prompt for client ID {client_id}.", "cyan"))
|
||||
input(colored("Press Enter to view the JSON that will be sent...", "green"))
|
||||
|
||||
# Pretty-printed JSON for the prompt
|
||||
print(colored("Here's the JSON that will be sent:", "yellow"))
|
||||
print(colored(json.dumps(p, indent=4), "blue")) # Pretty-printed JSON
|
||||
|
||||
input(colored("Press Enter to continue and send the prompt...", "green"))
|
||||
|
||||
return json.loads(urllib.request.urlopen(req).read())
|
||||
|
||||
# Get image function
|
||||
def get_image(filename, subfolder, folder_type):
|
||||
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
|
||||
url_values = urllib.parse.urlencode(data)
|
||||
|
||||
print(colored(f"Fetching image from the server: {server_address}/view", "cyan"))
|
||||
print(colored(f"Filename: {filename}, Subfolder: {subfolder}, Type: {folder_type}", "yellow"))
|
||||
with urllib.request.urlopen(f"http://{server_address}/view?{url_values}") as response:
|
||||
return response.read()
|
||||
|
||||
# Get history for a prompt ID
|
||||
def get_history(prompt_id):
|
||||
print(colored(f"Fetching history for prompt ID: {prompt_id}.", "cyan"))
|
||||
with urllib.request.urlopen(f"http://{server_address}/history/{prompt_id}") as response:
|
||||
return json.loads(response.read())
|
||||
|
||||
# Get images from the workflow
|
||||
def get_images(ws, prompt):
|
||||
prompt_id = queue_prompt(prompt)['prompt_id']
|
||||
output_images = {}
|
||||
|
||||
last_reported_percentage = 0
|
||||
|
||||
print(colored("Step 6: Start listening for progress updates via the WebSocket connection.", "cyan"))
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
while True:
|
||||
out = ws.recv()
|
||||
if isinstance(out, str):
|
||||
message = json.loads(out)
|
||||
if message['type'] == 'progress':
|
||||
data = message['data']
|
||||
current_progress = data['value']
|
||||
max_progress = data['max']
|
||||
percentage = int((current_progress / max_progress) * 100)
|
||||
|
||||
# Only update progress every 10%
|
||||
if percentage >= last_reported_percentage + 10:
|
||||
print(colored(f"Progress: {percentage}% in node {data['node']}", "yellow"))
|
||||
last_reported_percentage = percentage
|
||||
|
||||
elif message['type'] == 'executing':
|
||||
data = message['data']
|
||||
if data['node'] is None and data['prompt_id'] == prompt_id:
|
||||
print(colored("Execution complete.", "green"))
|
||||
break # Execution is done
|
||||
else:
|
||||
continue # Previews are binary data
|
||||
|
||||
# Fetch history and images after completion
|
||||
print(colored("Step 7: Fetch the history and download the images after execution completes.", "cyan"))
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
history = get_history(prompt_id)[prompt_id]
|
||||
for o in history['outputs']:
|
||||
for node_id in history['outputs']:
|
||||
node_output = history['outputs'][node_id]
|
||||
if 'images' in node_output:
|
||||
images_output = []
|
||||
for image in node_output['images']:
|
||||
print(colored(f"Downloading image: {image['filename']} from the server.", "yellow"))
|
||||
image_data = get_image(image['filename'], image['subfolder'], image['type'])
|
||||
images_output.append(image_data)
|
||||
output_images[node_id] = images_output
|
||||
|
||||
return output_images
|
||||
|
||||
# Generate images function with customizable input
|
||||
def generate_images(positive_prompt, negative_prompt, steps=25, resolution=(512, 512)):
|
||||
# Step 3: Establish WebSocket connection
|
||||
ws = websocket.WebSocket()
|
||||
ws_url = f"ws://{server_address}/ws?clientId={client_id}"
|
||||
print(colored(f"Step 3: Establishing WebSocket connection to {ws_url}", "cyan"))
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
ws.connect(ws_url)
|
||||
|
||||
# Step 4: Load workflow from file and print it
|
||||
print(colored("Step 4: Loading the image generation workflow from 'workflow.json'.", "cyan"))
|
||||
with open("workflow.json", "r", encoding="utf-8") as f:
|
||||
workflow_data = f.read()
|
||||
|
||||
workflow = json.loads(workflow_data)
|
||||
|
||||
input(colored("Press Enter to view the loaded workflow before customization...", "green"))
|
||||
|
||||
# Print the loaded workflow before customization
|
||||
print(colored("Here's the workflow as it was loaded before customization:", "yellow"))
|
||||
print(colored(json.dumps(workflow, indent=4), "blue")) # Pretty-print the workflow
|
||||
|
||||
input(colored("Press Enter to continue to customization...", "green"))
|
||||
|
||||
# Customize workflow based on inputs
|
||||
print(colored("Step 5: Customizing the workflow with the provided inputs.", "cyan"))
|
||||
print(colored(f"Setting positive prompt: {positive_prompt}", "yellow"))
|
||||
print(colored(f"Setting negative prompt: {negative_prompt}", "yellow"))
|
||||
workflow["6"]["inputs"]["text"] = positive_prompt
|
||||
workflow["7"]["inputs"]["text"] = negative_prompt
|
||||
|
||||
print(colored(f"Setting steps for generation: {steps}", "yellow"))
|
||||
workflow["3"]["inputs"]["steps"] = steps
|
||||
|
||||
print(colored(f"Setting resolution to {resolution[0]}x{resolution[1]}", "yellow"))
|
||||
workflow["5"]["inputs"]["width"] = resolution[0]
|
||||
workflow["5"]["inputs"]["height"] = resolution[1]
|
||||
|
||||
# Set a random seed for the KSampler node
|
||||
seed = random.randint(1, 1000000000)
|
||||
print(colored(f"Setting random seed for generation: {seed}", "yellow"))
|
||||
workflow["3"]["inputs"]["seed"] = seed
|
||||
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
# Fetch generated images
|
||||
images = get_images(ws, workflow)
|
||||
|
||||
# Step 8: Close WebSocket connection after fetching the images
|
||||
print(colored(f"Step 8: Closing WebSocket connection to {ws_url}", "cyan"))
|
||||
ws.close()
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
return images, seed
|
||||
|
||||
# Example of calling the method and saving the images
|
||||
if __name__ == "__main__":
|
||||
# Step 2: User input for prompts
|
||||
positive_prompt = input(colored("Enter the positive prompt: ", "cyan"))
|
||||
negative_prompt = input(colored("Enter the negative prompt: ", "cyan"))
|
||||
|
||||
print(colored("Step 2: User inputs the positive and negative prompts for image generation.", "cyan"))
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
# Call the generate_images function
|
||||
images, seed = generate_images(positive_prompt, negative_prompt)
|
||||
|
||||
# Step 9: Save the images
|
||||
print(colored("Step 9: Saving the generated images locally.", "cyan"))
|
||||
input(colored("Press Enter to continue...", "green"))
|
||||
|
||||
for node_id in images:
|
||||
for image_data in images[node_id]:
|
||||
image = Image.open(io.BytesIO(image_data))
|
||||
filename = f"outputs/{node_id}-{seed}.png"
|
||||
image.save(filename)
|
||||
print(colored(f"Image saved as {filename}", "blue"))
|
||||
28
On host/DreamCanvas/quick_prompts.json
Normal file
28
On host/DreamCanvas/quick_prompts.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"Positive Quick Prompts": [
|
||||
{ "label": "4k", "value": "4k" },
|
||||
{ "label": "Highly Detailed", "value": "highly detailed" },
|
||||
{ "label": "Hyperrealistic", "value": "hyperrealistic" }
|
||||
],
|
||||
"Negative Quick Prompts": [
|
||||
{ "label": "Blurry", "value": "blurry" },
|
||||
{ "label": "Watermark", "value": "watermark" },
|
||||
{ "label": "Low Quality", "value": "low quality" }
|
||||
],
|
||||
"Halloween Quick Prompts": [
|
||||
{ "label": "Black Background", "value": "black background" },
|
||||
{ "label": "Silhouette", "value": "Silhouette" },
|
||||
{ "label": "Witch", "value": "Witch" },
|
||||
{ "label": "Moon", "value": "Moon" },
|
||||
{ "label": "Halloween", "value": "Halloween" },
|
||||
{ "label": "Seance", "value": "Seance" }
|
||||
],
|
||||
"Christmas Quick Prompts": [
|
||||
{ "label": "Snowy", "value": "Snowy" },
|
||||
{ "label": "Christmas Tree", "value": "Christmas Tree" },
|
||||
{ "label": "Santa Claus", "value": "Santa Claus" },
|
||||
{ "label": "Presents", "value": "Presents" },
|
||||
{ "label": "Winter Wonderland", "value": "Winter Wonderland" }
|
||||
]
|
||||
}
|
||||
|
||||
9
On host/DreamCanvas/requirements.txt
Normal file
9
On host/DreamCanvas/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
websocket-client
|
||||
pillow
|
||||
python-multipart
|
||||
fastapi
|
||||
uvicorn
|
||||
websocket-client
|
||||
termcolor
|
||||
requests
|
||||
python-dotenv
|
||||
97
On host/DreamCanvas/ui/index.html
Normal file
97
On host/DreamCanvas/ui/index.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DreamCanvas</title>
|
||||
<!-- Using Bootstrap's Cyborg theme -->
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cyborg/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="mb-4 text-left text-md-left text-center" style="font-size: 2rem;">DreamCanvas</h1>
|
||||
|
||||
<div class="row">
|
||||
<!-- Form Section -->
|
||||
<div class="col-md-6">
|
||||
<!-- Notification Banner for LLM Success -->
|
||||
<div id="notification" class="alert alert-success d-none" role="alert">
|
||||
LLM's creative prompt has been applied!
|
||||
</div>
|
||||
|
||||
<!-- Image Generation Form -->
|
||||
<form id="imageForm">
|
||||
<div class="mb-3">
|
||||
<label for="positivePrompt" class="form-label">Positive Prompt</label>
|
||||
<input type="text" class="form-control" id="positivePrompt" placeholder="Describe what you want">
|
||||
<small class="form-text text-muted">Examples: 4k, highly detailed, hyperrealistic</small>
|
||||
<div class="keyword-suggestions" id="positiveKeywords"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="negativePrompt" class="form-label">Negative Prompt</label>
|
||||
<input type="text" class="form-control" id="negativePrompt" placeholder="Describe what to avoid">
|
||||
<small class="form-text text-muted">Examples: blurry, watermark</small>
|
||||
<div class="keyword-suggestions" id="negativeKeywords"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="llmResponse" class="form-label">Creative Prompt from LLM</label>
|
||||
<textarea class="form-control" id="llmResponse" rows="3" readonly></textarea>
|
||||
<button type="button" id="askLLMButton" class="btn btn-info mt-3">
|
||||
<span id="askLLMText">Ask LLM for Creative Idea</span>
|
||||
<span id="askLLMSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" id="useLLMResponseButton" class="btn btn-success mt-3 d-none">Use LLM's Creative Prompt</button>
|
||||
</div>
|
||||
|
||||
<div id="quickPromptsContainer" class="mb-3"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="steps" class="form-label">Steps</label>
|
||||
<input type="number" class="form-control" id="steps" value="25" min="1">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="width" class="form-label">Width</label>
|
||||
<input type="number" class="form-control" id="width" value="512">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="height" class="form-label">Height</label>
|
||||
<input type="number" class="form-control" id="height" value="512">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="generateBtn" class="btn btn-primary w-100 mb-3">
|
||||
<span id="buttonText">Generate Image</span>
|
||||
<span id="spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||
<span id="elapsedTime" class="d-none">(00:00)</span>
|
||||
</button>
|
||||
<button type="button" id="resetBtn" class="btn btn-secondary w-100">Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Image Display Section -->
|
||||
<div class="col-md-6">
|
||||
<div id="imageResult" class="sticky-top mt-4">
|
||||
<img id="generatedImage" src="" alt="Generated Image" class="img-fluid d-none">
|
||||
<div id="imageNavigation" class="mt-3 d-none text-center">
|
||||
<button type="button" id="prevImage" class="btn btn-outline-light me-2">Previous</button>
|
||||
<button type="button" id="nextImage" class="btn btn-outline-light">Next</button>
|
||||
</div>
|
||||
<div id="promptDisplay" class="mt-3 d-none">
|
||||
<h5>Positive Prompts:</h5>
|
||||
<p id="positivePromptDisplay" class="text-success"></p>
|
||||
<h5>Negative Prompts:</h5>
|
||||
<p id="negativePromptDisplay" class="text-danger"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
316
On host/DreamCanvas/ui/script.js
Normal file
316
On host/DreamCanvas/ui/script.js
Normal file
@@ -0,0 +1,316 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("imageForm");
|
||||
const generatedImage = document.getElementById("generatedImage");
|
||||
const generateBtn = document.getElementById("generateBtn");
|
||||
const spinner = document.getElementById("spinner");
|
||||
const buttonText = document.getElementById("buttonText");
|
||||
const elapsedTime = document.getElementById("elapsedTime");
|
||||
const positivePromptDisplay = document.getElementById("positivePromptDisplay");
|
||||
const negativePromptDisplay = document.getElementById("negativePromptDisplay");
|
||||
const promptDisplay = document.getElementById("promptDisplay");
|
||||
const quickPromptsContainer = document.getElementById("quickPromptsContainer");
|
||||
const positiveKeywordsContainer = document.getElementById("positiveKeywords");
|
||||
const negativeKeywordsContainer = document.getElementById("negativeKeywords");
|
||||
const askLLMButton = document.getElementById("askLLMButton");
|
||||
const askLLMSpinner = document.getElementById("askLLMSpinner");
|
||||
const askLLMText = document.getElementById("askLLMText");
|
||||
const llmResponseTextarea = document.getElementById("llmResponse");
|
||||
const useLLMResponseButton = document.getElementById("useLLMResponseButton");
|
||||
const notification = document.getElementById("notification");
|
||||
const resetBtn = document.getElementById("resetBtn");
|
||||
const imageNavigation = document.getElementById("imageNavigation");
|
||||
const prevImageBtn = document.getElementById("prevImage");
|
||||
const nextImageBtn = document.getElementById("nextImage");
|
||||
|
||||
let timerInterval;
|
||||
let startTime;
|
||||
let imageHistory = []; // Cache to store generated images
|
||||
let currentImageIndex = -1; // Current index in image history
|
||||
|
||||
// Function to start the timer
|
||||
function startTimer() {
|
||||
startTime = new Date().getTime(); // Reset the start time
|
||||
elapsedTime.innerText = "(00:00)"; // Reset display time
|
||||
elapsedTime.classList.remove("d-none");
|
||||
timerInterval = setInterval(function () {
|
||||
const now = new Date().getTime();
|
||||
const distance = now - startTime;
|
||||
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
|
||||
elapsedTime.innerText = `(${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')})`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Function to stop the timer
|
||||
function stopTimer() {
|
||||
clearInterval(timerInterval);
|
||||
elapsedTime.classList.add("d-none");
|
||||
}
|
||||
|
||||
// Function to display the notification for LLM usage
|
||||
function showNotification(message, type = 'success') {
|
||||
notification.innerText = message;
|
||||
notification.classList.remove("d-none", "alert-success", "alert-danger");
|
||||
notification.classList.add(`alert-${type}`);
|
||||
|
||||
// Hide the notification after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.classList.add("d-none");
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Automatically scroll to the image result when it is generated
|
||||
function scrollToImage() {
|
||||
generatedImage.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
|
||||
// Function to reset the form
|
||||
resetBtn.addEventListener("click", function () {
|
||||
document.getElementById("positivePrompt").value = "";
|
||||
document.getElementById("negativePrompt").value = "";
|
||||
llmResponseTextarea.value = "";
|
||||
generatedImage.classList.add("d-none");
|
||||
promptDisplay.classList.add("d-none");
|
||||
useLLMResponseButton.classList.add("d-none");
|
||||
spinner.classList.add("d-none");
|
||||
buttonText.innerText = "Generate Image";
|
||||
imageHistory = [];
|
||||
currentImageIndex = -1;
|
||||
imageNavigation.classList.add("d-none");
|
||||
showNotification("The UI has been reset.");
|
||||
});
|
||||
|
||||
// Function to dynamically load quick prompts from the server
|
||||
function loadQuickPrompts() {
|
||||
fetch("/quick_prompts/")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
quickPromptsContainer.innerHTML = ''; // Clear any existing quick prompts
|
||||
positiveKeywordsContainer.innerHTML = ''; // Clear positive prompt buttons
|
||||
negativeKeywordsContainer.innerHTML = ''; // Clear negative prompt buttons
|
||||
|
||||
// Load Positive Quick Prompts
|
||||
if (data["Positive Quick Prompts"]) {
|
||||
data["Positive Quick Prompts"].forEach(prompt => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('btn', 'btn-secondary', 'btn-sm', 'me-2', 'mb-2');
|
||||
button.innerText = prompt.label;
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
addPositiveKeyword(button, prompt.value);
|
||||
});
|
||||
|
||||
positiveKeywordsContainer.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
// Load Negative Quick Prompts
|
||||
if (data["Negative Quick Prompts"]) {
|
||||
data["Negative Quick Prompts"].forEach(prompt => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('btn', 'btn-secondary', 'btn-sm', 'me-2', 'mb-2');
|
||||
button.innerText = prompt.label;
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
addNegativeKeyword(button, prompt.value);
|
||||
});
|
||||
|
||||
negativeKeywordsContainer.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
// Load Other Quick Prompt Categories (e.g., Halloween, Christmas)
|
||||
for (const category in data) {
|
||||
if (category !== "Positive Quick Prompts" && category !== "Negative Quick Prompts") {
|
||||
const section = document.createElement('div');
|
||||
section.classList.add('mb-3');
|
||||
|
||||
const heading = document.createElement('label');
|
||||
heading.classList.add('form-label');
|
||||
heading.innerText = category;
|
||||
|
||||
const buttonsContainer = document.createElement('div');
|
||||
buttonsContainer.classList.add('keyword-suggestions');
|
||||
|
||||
// Generate buttons for each prompt
|
||||
data[category].forEach(prompt => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('btn', 'btn-secondary', 'btn-sm', 'me-2', 'mb-2');
|
||||
button.innerText = prompt.label;
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
addPositiveKeyword(button, prompt.value);
|
||||
});
|
||||
|
||||
buttonsContainer.appendChild(button);
|
||||
});
|
||||
|
||||
section.appendChild(heading);
|
||||
section.appendChild(buttonsContainer);
|
||||
quickPromptsContainer.appendChild(section);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error loading quick prompts:", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to add keywords to input fields and disable buttons after selection
|
||||
window.addPositiveKeyword = function (button, keyword) {
|
||||
const positiveInput = document.getElementById("positivePrompt");
|
||||
positiveInput.value = `${positiveInput.value} ${keyword}`.trim();
|
||||
button.disabled = true; // Disable button after adding keyword
|
||||
};
|
||||
|
||||
window.addNegativeKeyword = function (button, keyword) {
|
||||
const negativeInput = document.getElementById("negativePrompt");
|
||||
negativeInput.value = `${negativeInput.value} ${keyword}`.trim();
|
||||
button.disabled = true; // Disable button after adding keyword
|
||||
};
|
||||
|
||||
// Function to ask the LLM for a creative prompt
|
||||
askLLMButton.addEventListener("click", function () {
|
||||
const positivePrompt = document.getElementById("positivePrompt").value;
|
||||
|
||||
let promptToSend = positivePrompt.trim();
|
||||
if (!promptToSend) {
|
||||
promptToSend = "Generate a general creative idea.";
|
||||
}
|
||||
|
||||
// Disable button and show spinner
|
||||
askLLMButton.disabled = true;
|
||||
askLLMSpinner.classList.remove("d-none");
|
||||
askLLMText.innerText = "Processing...";
|
||||
|
||||
fetch("/ask_llm/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ positive_prompt: promptToSend })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
llmResponseTextarea.value = data.assistant_reply;
|
||||
useLLMResponseButton.classList.remove("d-none"); // Show the button to use the LLM's response
|
||||
|
||||
// Re-enable button and hide spinner
|
||||
askLLMButton.disabled = false;
|
||||
askLLMSpinner.classList.add("d-none");
|
||||
askLLMText.innerText = "Ask LLM for Creative Idea";
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error getting LLM response:", error);
|
||||
askLLMButton.disabled = false;
|
||||
askLLMSpinner.classList.add("d-none");
|
||||
askLLMText.innerText = "Ask LLM for Creative Idea";
|
||||
});
|
||||
});
|
||||
|
||||
// Function to use the LLM's creative response as the positive prompt
|
||||
useLLMResponseButton.addEventListener("click", function () {
|
||||
const llmResponse = llmResponseTextarea.value;
|
||||
document.getElementById("positivePrompt").value = llmResponse;
|
||||
showNotification("LLM's creative prompt has been applied!");
|
||||
});
|
||||
|
||||
// Form submission for generating image
|
||||
form.addEventListener("submit", function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Reset the timer and disable the Generate button
|
||||
generateBtn.disabled = true;
|
||||
spinner.classList.remove("d-none");
|
||||
buttonText.innerText = "Generating...";
|
||||
startTimer(); // Start timer with a reset
|
||||
|
||||
// Get input values
|
||||
const positivePrompt = document.getElementById("positivePrompt").value;
|
||||
const negativePrompt = document.getElementById("negativePrompt").value;
|
||||
const steps = document.getElementById("steps").value;
|
||||
const width = document.getElementById("width").value;
|
||||
const height = document.getElementById("height").value;
|
||||
|
||||
// Post image generation request
|
||||
fetch("/generate_images/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
steps: parseInt(steps),
|
||||
width: parseInt(width),
|
||||
height: parseInt(height)
|
||||
})
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(imageBlob => {
|
||||
const imageUrl = URL.createObjectURL(imageBlob);
|
||||
generatedImage.src = imageUrl;
|
||||
generatedImage.classList.remove("d-none");
|
||||
|
||||
// Cache the image in imageHistory and update the index
|
||||
imageHistory.push(imageUrl);
|
||||
currentImageIndex = imageHistory.length - 1;
|
||||
updateImageNavigation();
|
||||
scrollToImage(); // Automatically scroll to the image
|
||||
|
||||
// Display the selected prompts
|
||||
positivePromptDisplay.innerText = positivePrompt;
|
||||
negativePromptDisplay.innerText = negativePrompt;
|
||||
promptDisplay.classList.remove("d-none");
|
||||
|
||||
// Reset the button and timer
|
||||
generateBtn.disabled = false;
|
||||
spinner.classList.add("d-none");
|
||||
buttonText.innerText = "Generate Image";
|
||||
stopTimer();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error generating image:", error);
|
||||
generateBtn.disabled = false;
|
||||
spinner.classList.add("d-none");
|
||||
buttonText.innerText = "Generate Image";
|
||||
stopTimer();
|
||||
showNotification("Failed to generate image. Please try again.", "danger");
|
||||
});
|
||||
});
|
||||
|
||||
// Function to update image navigation visibility and state
|
||||
function updateImageNavigation() {
|
||||
if (imageHistory.length > 1) {
|
||||
imageNavigation.classList.remove("d-none");
|
||||
} else {
|
||||
imageNavigation.classList.add("d-none");
|
||||
}
|
||||
|
||||
prevImageBtn.disabled = currentImageIndex <= 0;
|
||||
nextImageBtn.disabled = currentImageIndex >= imageHistory.length - 1;
|
||||
}
|
||||
|
||||
// Previous and Next Image Navigation
|
||||
prevImageBtn.addEventListener("click", function () {
|
||||
if (currentImageIndex > 0) {
|
||||
currentImageIndex--;
|
||||
generatedImage.src = imageHistory[currentImageIndex];
|
||||
}
|
||||
updateImageNavigation();
|
||||
});
|
||||
|
||||
nextImageBtn.addEventListener("click", function () {
|
||||
if (currentImageIndex < imageHistory.length - 1) {
|
||||
currentImageIndex++;
|
||||
generatedImage.src = imageHistory[currentImageIndex];
|
||||
}
|
||||
updateImageNavigation();
|
||||
});
|
||||
|
||||
// Load quick prompts when the page is loaded
|
||||
loadQuickPrompts();
|
||||
});
|
||||
82
On host/DreamCanvas/ui/styles.css
Normal file
82
On host/DreamCanvas/ui/styles.css
Normal file
@@ -0,0 +1,82 @@
|
||||
body {
|
||||
background-color: #1e1e1e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #f7c04a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
color: #f7c04a;
|
||||
}
|
||||
|
||||
.keyword-suggestions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#generatedImage {
|
||||
border: 2px solid #f7c04a;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#promptDisplay {
|
||||
background-color: #292929;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #f7c04a;
|
||||
}
|
||||
|
||||
#positivePromptDisplay {
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#negativePromptDisplay {
|
||||
color: #ff3333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#prevImage, #nextImage {
|
||||
border-color: #f7c04a; /* Gold border */
|
||||
background-color: transparent; /* Transparent background */
|
||||
color: #f7c04a; /* Gold text color */
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
#prevImage:hover, #nextImage:hover {
|
||||
background-color: #f7c04a; /* Gold background on hover */
|
||||
color: #1e1e1e; /* Dark text on hover to contrast */
|
||||
}
|
||||
|
||||
#llmResponse {
|
||||
margin-top: 0; /* Remove any extra margin at the top */
|
||||
padding-top: 0; /* Remove extra padding at the top */
|
||||
line-height: 1.5; /* Adjust line height for better readability */
|
||||
text-align: left; /* Ensure the text aligns to the left */
|
||||
}
|
||||
|
||||
#llmResponse:focus {
|
||||
outline: none; /* Remove the blue outline when focused */
|
||||
box-shadow: 0 0 5px 2px #f7c04a; /* Optional: Add a gold glow when focused */
|
||||
}
|
||||
|
||||
/* Center the title on smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#imageResult {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* New styles for responsive layout and sticky image preview */
|
||||
.sticky-top {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
107
On host/DreamCanvas/workflow.json
Normal file
107
On host/DreamCanvas/workflow.json
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 680159598837398,
|
||||
"steps": 25,
|
||||
"cfg": 8,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1,
|
||||
"model": [
|
||||
"4",
|
||||
0
|
||||
],
|
||||
"positive": [
|
||||
"6",
|
||||
0
|
||||
],
|
||||
"negative": [
|
||||
"7",
|
||||
0
|
||||
],
|
||||
"latent_image": [
|
||||
"5",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "KSampler",
|
||||
"_meta": {
|
||||
"title": "KSampler"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "realvisxlV50Lightning.Ng9I.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"_meta": {
|
||||
"title": "Load Checkpoint"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"inputs": {
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"batch_size": 1
|
||||
},
|
||||
"class_type": "EmptyLatentImage",
|
||||
"_meta": {
|
||||
"title": "Empty Latent Image"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "gorgeous woman, blonde , 8k, detailed",
|
||||
"clip": [
|
||||
"4",
|
||||
1
|
||||
]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP Text Encode (Prompt)"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"text": "text, watermark",
|
||||
"clip": [
|
||||
"4",
|
||||
1
|
||||
]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP Text Encode (Prompt)"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": [
|
||||
"3",
|
||||
0
|
||||
],
|
||||
"vae": [
|
||||
"4",
|
||||
2
|
||||
]
|
||||
},
|
||||
"class_type": "VAEDecode",
|
||||
"_meta": {
|
||||
"title": "VAE Decode"
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": [
|
||||
"8",
|
||||
0
|
||||
]
|
||||
},
|
||||
"class_type": "SaveImage",
|
||||
"_meta": {
|
||||
"title": "Save Image"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user