adds a simple WebUI for the evaluation of models

This commit is contained in:
julian 2025-01-29 00:50:22 -05:00
parent bfa6c0e1c1
commit 1f83d279a1
8 changed files with 509 additions and 0 deletions

View File

@ -1,5 +1,6 @@
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
@ -8,7 +9,12 @@ 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 = os.getenv("MODEL_NAME", "deepseek-ai/Janus-1.3B")

164
demo/webui/APIChat.js Normal file
View File

@ -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();
}
});
});

BIN
demo/webui/child.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
demo/webui/favicon.ico Normal file

Binary file not shown.

304
demo/webui/index.css Normal file
View File

@ -0,0 +1,304 @@
/* Ensure full-screen, no scrolling */
html, body {
height: 100vh;
width: 100vw;
margin: 0;
padding: 0;
overflow: hidden; /* Prevent scrolling */
background-color: black; /* Match chat background */
font-family: Arial, sans-serif; /* Improve readability */
}
/* Chat container setup */
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
box-sizing: border-box;
}
/* Center chat box */
.chat-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #000;
overflow: hidden;
padding: 20px;
}
/* Response messages container */
.responses-container {
overflow-y: auto;
align-items: center;
width: 40%;
height: 100%;
border-radius: 1rem;
border: 1px solid #ccc;
background-color: #1e1e1e;
margin: 0 auto; /* Center horizontally */
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.chat-box {
padding: 10px;
}
.responses-container {
width: 100vw; /* Full width */
max-width: none;
border-radius: 0;
border: none;
height: calc(100vh - 120px); /* Adjust height for input and header */
}
}
/* Input container */
.input-container {
display: flex;
padding: 10px;
width: 100%;
background-color: #242424;
flex-shrink: 0; /* Prevent shrinking */
}
/* Individual chat message */
.chat-message {
display: flex;
align-items: top;
margin-bottom: 5px;
padding: 10px;
word-wrap: break-word;
position: relative; /* For positioning options popup */
}
/* Show message options on hover */
.chat-message:hover .message-options {
display: flex;
}
/* Avatar styling */
.message-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.message-avatar .default-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #ccc;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
margin-right: 10px;
}
/* Message content */
.message-content {
padding: 0;
width: auto;
max-width: 80%;
margin: 0;
}
/* Sender name */
.message-sender {
font-weight: bold;
margin-bottom: 2px;
color: var(--arkavo-orange, red);
}
/* Timestamp */
.message-timestamp {
font-size: 0.8em;
color: #888;
margin-left: 8px;
}
/* Message body */
.message-body {
background-color: #333;
color:#eee;
padding: 8px 12px;
border-radius: 8px;
word-wrap: break-word;
}
/* Message options */
.message-options {
display: none;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background-color: #444;
border-radius: 4px;
padding: 4px;
gap: 4px;
}
/* Option button */
.option-button {
background-color: #555;
color: #fff;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
}
.option-button:hover {
background-color: #666;
}
/* Chat input field */
.chat-input {
flex: 1;
padding: 10px;
border: 1px solid #444;
border-radius: 8px;
resize: none;
font-size: 1.5rem;
margin-right: 10px;
background-color: #333;
color: #fff;
}
/* Send button */
.send-button {
padding: 10px 15px;
background-color: var(--arkavo-dark-orange, red);
color: #fff;
border: none;
font-size: 1.5rem;
border-radius: 8px;
cursor: pointer;
}
.send-button:hover {
background-color: darkred;
}
/* Combined input field */
.combined-input {
position: relative;
align-items: center;
font-size: 1.5rem;
width: 100%;
}
.combined-input .chat-input {
flex: 1;
padding-right: 40px;
border: 1px solid #ccc;
font-size: 1.5rem;
border-radius: 4px;
padding: 8px;
}
/* Dropdown button */
.dropdown-button {
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 40px;
color: #333;
font-size: 1.5rem;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-left: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.dropdown-button:hover {
background-color: #e0e0e0;
}
/* Dropdown menu */
.dropdown-menu {
position: absolute;
background: black;
border: 1px solid #ccc;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
font-size: 1.5rem;
width: 100%;
z-index: 2000;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
}
/* Dropdown item */
.dropdown-item {
display: flex;
font-size: 1.5rem;
justify-content: space-between;
align-items: center;
padding: 8px;
background: #333;
color: #f0f0f0;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.dropdown-item:hover {
background: #f0f0f0;
color: #333;
}
/* Delete button */
.delete-button {
background: none;
border: none;
cursor: pointer;
font-size: 1.5rem;
color: red;
}
.delete-button:hover {
color: darkred;
}
/* Scrollbar for Webkit browsers (Chrome, Edge, Safari) */
::-webkit-scrollbar {
width: 8px; /* Adjust width */
height: 8px; /* Adjust height for horizontal scrollbars */
}
/* Scrollbar track (background) */
::-webkit-scrollbar-track {
background: #1a1a1a; /* Dark background */
border-radius: 4px; /* Rounded corners */
}
/* Scrollbar thumb (draggable handle) */
::-webkit-scrollbar-thumb {
background: #666; /* Greyish thumb */
border-radius: 4px; /* Rounded corners */
transition: background 0.3s ease-in-out;
}
/* Hover effect */
::-webkit-scrollbar-thumb:hover {
background: #888; /* Lighter grey on hover */
}
/* Firefox Scrollbar */
* {
scrollbar-width: thin; /* Thinner scrollbar */
scrollbar-color: #666 #1a1a1a; /* Thumb color, Track color */
}

35
demo/webui/index.html Normal file
View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Interface</title>
<link rel="stylesheet" href="./index.css">
<link rel="icon" type="image/png" href="/webui/favicon.ico">
</head>
<body>
<main class="chat-container">
<div class="chat-box">
<div class="input-container">
<div class="combined-input">
<input id="apiUrlInput" class="chat-input" type="text" placeholder="Enter API URL" style="width: 100%;">
<button id="dropdownButton" class="dropdown-button"></button>
<div id="dropdownMenu" class="dropdown-menu" style="display: none;"></div>
</div>
</div>
<div id="responsesContainer" class="responses-container"></div>
<div class="input-container">
<textarea id="promptInput" class="chat-input" placeholder="Enter your prompt for image generation" rows="3"></textarea>
<button id="sendButton" class="send-button">Send</button>
</div>
<div id="errorMessage" class="error-message" style="display: none;"></div>
</div>
</main>
<script type="text/javascript" src="./APIChat.js"></script>
</body>
</html>

BIN
demo/webui/system.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
demo/webui/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB