Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker and和 WebUI #38

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -418,4 +418,5 @@ tags
[._]*.un~
.vscode
.github
generated_samples/
generated_samples/
huggingface
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Use the PyTorch base image
FROM pytorch/pytorch:latest

# Set the working directory inside the container
WORKDIR /app

# Copy the current directory into the container
COPY . /app

# Install necessary Python packages
RUN pip install -e . && \
pip install fastapi python-multipart uvicorn

# Set the entrypoint for the container to launch your FastAPI app
CMD ["python", "demo/fastapi_app.py"]

13 changes: 10 additions & 3 deletions demo/fastapi_app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from fastapi import FastAPI, File, Form, UploadFile, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
import torch
from transformers import AutoConfig, AutoModelForCausalLM
from janus.models import MultiModalityCausalLM, VLChatProcessor
from PIL import Image
import numpy as np
import io
import os

# Resolve absolute path based on the script's location
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # This gets "demo/"
WEBUI_DIR = os.path.join(BASE_DIR, "webui") # Moves up to the project root

app = FastAPI()
app.mount("/webui", StaticFiles(directory=WEBUI_DIR, html=True), name="webui")

# Load model and processor
model_path = "deepseek-ai/Janus-1.3B"
model_path = os.getenv("MODEL_NAME", "deepseek-ai/Janus-1.3B")
config = AutoConfig.from_pretrained(model_path)
language_config = config.language_config
language_config._attn_implementation = 'eager'
Expand Down Expand Up @@ -63,7 +70,7 @@ def multimodal_understanding(image_data, question, seed, top_p, temperature):
return answer


@app.post("/understand_image_and_question/")
@app.post("/understand_image_and_question")
async def understand_image_and_question(
file: UploadFile = File(...),
question: str = Form(...),
Expand Down Expand Up @@ -152,7 +159,7 @@ def generate_image(prompt, seed, guidance):
return [Image.fromarray(images[i]).resize((1024, 1024), Image.LANCZOS) for i in range(parallel_size)]


@app.post("/generate_images/")
@app.post("/generate_images")
async def generate_images(
prompt: str = Form(...),
seed: int = Form(None),
Expand Down
164 changes: 164 additions & 0 deletions demo/webui/APIChat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
document.addEventListener('DOMContentLoaded', function () {
const apiUrlInput = document.getElementById('apiUrlInput');
const dropdownButton = document.getElementById('dropdownButton');
const dropdownMenu = document.getElementById('dropdownMenu');
const responsesContainer = document.getElementById('responsesContainer');
const promptInput = document.getElementById('promptInput');
const sendButton = document.getElementById('sendButton');
const errorMessage = document.getElementById('errorMessage');

const defaultApiUrl = window.location.origin + '/generate_images';

let apiUrl = localStorage.getItem('lastUsedUrl') || defaultApiUrl;
let urlHistory = JSON.parse(localStorage.getItem('urlHistory')) || [];

// If urlHistory is empty, add the default localhost URL
if (urlHistory.length === 0) {
saveUrl(defaultApiUrl)
}

apiUrlInput.value = apiUrl;
updateDropdown();

function updateDropdown() {
dropdownMenu.innerHTML = urlHistory.length
? urlHistory.map(url => `<div class="dropdown-item">
<span class="select-url">${url}</span>
<button class="delete-button">🗑️</button>
</div>`).join('')
: '<div class="dropdown-item">No history</div>';
}

function addMessage(sender, body, avatarUrl) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('chat-message');

const avatarDiv = document.createElement('div');
avatarDiv.classList.add('message-avatar');
avatarDiv.innerHTML = avatarUrl ? `<img src="${avatarUrl}" alt="Avatar">` : `<div class="default-avatar">${sender[0]}</div>`;

const messageContent = document.createElement('div');
messageContent.classList.add('message-content');

// Generate timestamp
const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });

messageContent.innerHTML = `
<div class="message-header">
<span class="message-sender">${sender}</span>
<span class="message-timestamp">${timestamp}</span>
</div>
<div class="message-body"></div>
`;

const messageBody = messageContent.querySelector('.message-body');
if (typeof body === 'string') {
messageBody.textContent = body;
} else {
messageBody.appendChild(body);
}

messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(messageContent);
responsesContainer.appendChild(messageDiv);
responsesContainer.scrollTop = responsesContainer.scrollHeight;
}


dropdownButton.addEventListener('click', () => {
dropdownMenu.style.display = dropdownMenu.style.display === 'none' ? 'block' : 'none';
});

dropdownMenu.addEventListener('click', (e) => {
if (e.target.classList.contains('select-url')) {
apiUrl = e.target.textContent;
apiUrlInput.value = apiUrl;
saveUrl(apiUrl);
dropdownMenu.style.display = 'none';
} else if (e.target.classList.contains('delete-button')) {
const parent = e.target.closest('.dropdown-item');
const urlToDelete = parent.querySelector('.select-url').textContent;
urlHistory = urlHistory.filter(url => url !== urlToDelete);
localStorage.setItem('urlHistory', JSON.stringify(urlHistory));
updateDropdown();
}
});

apiUrlInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
apiUrl = apiUrlInput.value.trim(); // Ensure the latest value is saved
saveUrl(apiUrl);
}
});

function saveUrl(url) {
if (!url.trim()) return;

// Set the last used URL
localStorage.setItem('lastUsedUrl', url);

// Add to history if it doesn't exist
if (!urlHistory.includes(url)) {
urlHistory.push(url);
localStorage.setItem('urlHistory', JSON.stringify(urlHistory));
updateDropdown();
}

addMessage('System', `API URL set to: ${url}`, './system.png');
}
async function generateImage() {
const prompt = promptInput.value.trim();
if (!prompt) return;

addMessage('You', prompt, './user.png');
promptInput.value = '';
sendButton.disabled = true;
sendButton.textContent = 'Generating...';
errorMessage.style.display = 'none';

try {
const params = new URLSearchParams();
params.set('prompt', prompt);
params.set('seed', Math.floor(Math.random() * 1000000).toString());
params.set('guidance', '5');

const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to generate image. Status: ${response.status}, Response: ${errorText}`);
}

const blob = await response.blob();
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
img.style.maxWidth = '100%';
img.style.borderRadius = '8px';

addMessage('AI', img, './child.jpg');
} catch (err) {
addMessage('System', `Error generating image: ${err.message}`, './system.png');
errorMessage.textContent = `Error generating image: ${err.message}`;
errorMessage.style.display = 'block';
} finally {
sendButton.disabled = false;
sendButton.textContent = 'Send';
}
}

sendButton.addEventListener('click', generateImage);

promptInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
generateImage();
}
});
});
Binary file added demo/webui/child.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/webui/favicon.ico
Binary file not shown.
Loading