llm-project-1 / index.html
jerwng's picture
Update index.html
7ffd79c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local Inference UI</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f0f23;
background-image:
radial-gradient(at 20% 30%, rgba(59, 130, 246, 0.15) 0px, transparent 50%),
radial-gradient(at 80% 70%, rgba(139, 92, 246, 0.15) 0px, transparent 50%),
radial-gradient(at 50% 50%, rgba(16, 185, 129, 0.1) 0px, transparent 50%);
min-height: 100vh;
padding: 40px 20px;
color: #e5e7eb;
}
.container {
max-width: 900px;
margin: 0 auto;
background: rgba(31, 41, 55, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(75, 85, 99, 0.3);
border-radius: 20px;
padding: 40px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
h1 {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #10b981 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 40px;
text-align: center;
font-size: 2.5em;
font-weight: 800;
letter-spacing: -0.02em;
}
.model-selector {
display: flex;
gap: 20px;
margin-bottom: 35px;
padding: 24px;
background: rgba(17, 24, 39, 0.6);
border: 1px solid rgba(75, 85, 99, 0.3);
border-radius: 12px;
backdrop-filter: blur(5px);
}
.model-option {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.model-option label {
font-weight: 600;
color: #d1d5db;
display: flex;
align-items: center;
gap: 10px;
}
.toggle {
position: relative;
display: inline-block;
width: 56px;
height: 28px;
}
.toggle input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
transition: 0.3s;
border-radius: 28px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.slider:before {
position: absolute;
content: "";
height: 22px;
width: 22px;
left: 3px;
bottom: 3px;
background-color: #fff;
transition: 0.3s;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
input:checked + .slider {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
}
input:checked + .slider:before {
transform: translateX(28px);
}
.model-status {
font-size: 0.9em;
color: #9ca3af;
}
.input-section {
margin-bottom: 25px;
}
label {
display: block;
margin-bottom: 10px;
font-weight: 600;
color: #d1d5db;
font-size: 0.95em;
letter-spacing: 0.01em;
}
textarea {
width: 100%;
padding: 16px;
background: rgba(17, 24, 39, 0.6);
border: 1px solid rgba(75, 85, 99, 0.5);
border-radius: 10px;
font-size: 15px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
resize: vertical;
min-height: 120px;
color: #e5e7eb;
transition: all 0.2s;
}
textarea:focus {
outline: none;
border-color: #3b82f6;
background: rgba(17, 24, 39, 0.8);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
textarea::placeholder {
color: #6b7280;
}
.controls {
display: flex;
gap: 20px;
margin-bottom: 25px;
align-items: flex-end;
}
.control-group {
flex: 1;
}
input[type="number"],
select {
width: 100%;
padding: 12px 14px;
background: rgba(17, 24, 39, 0.6);
border: 1px solid rgba(75, 85, 99, 0.5);
border-radius: 10px;
font-size: 14px;
color: #e5e7eb;
transition: all 0.2s;
}
input[type="number"]:focus,
select:focus {
outline: none;
border-color: #3b82f6;
background: rgba(17, 24, 39, 0.8);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
select {
cursor: pointer;
}
select option {
background: #1f2937;
color: #e5e7eb;
}
button {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
color: white;
border: none;
padding: 14px 36px;
font-size: 16px;
font-weight: 700;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
letter-spacing: 0.02em;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.4);
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
}
button:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
button:disabled {
background: rgba(75, 85, 99, 0.5);
cursor: not-allowed;
transform: none;
box-shadow: none;
opacity: 0.6;
}
.output-section {
margin-top: 35px;
}
.output-box {
background: rgba(17, 24, 39, 0.6);
border: 1px solid rgba(75, 85, 99, 0.5);
border-radius: 10px;
padding: 20px;
min-height: 140px;
white-space: pre-wrap;
word-wrap: break-word;
font-size: 15px;
line-height: 1.7;
color: #d1d5db;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
}
.loading {
text-align: center;
color: #3b82f6;
font-style: italic;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.error {
color: #fca5a5;
background: rgba(127, 29, 29, 0.3);
border-color: #ef4444;
}
/* Tooltip styles for alternative words */
.generated-word {
position: relative;
display: inline;
cursor: pointer;
padding: 2px 4px;
border-radius: 4px;
transition: background-color 0.2s;
background-color: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.3);
margin: 0 1px;
}
.generated-word:hover {
background-color: rgba(59, 130, 246, 0.2);
border-color: rgba(59, 130, 246, 0.5);
}
.tooltip {
visibility: hidden;
opacity: 0;
position: absolute;
z-index: 1000;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background-color: rgba(17, 24, 39, 0.95);
color: #e5e7eb;
text-align: center;
padding: 12px 16px;
border-radius: 8px;
border: 1px solid rgba(75, 85, 99, 0.5);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
backdrop-filter: blur(10px);
min-width: 200px;
max-width: 300px;
transition: opacity 0.3s, visibility 0.3s;
font-size: 13px;
line-height: 1.4;
}
.tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: rgba(17, 24, 39, 0.95) transparent transparent transparent;
}
.generated-word:hover .tooltip {
visibility: visible;
opacity: 1;
}
.tooltip-header {
font-weight: 600;
margin-bottom: 8px;
color: #3b82f6;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.alternative-word {
display: inline-block;
background: rgba(75, 85, 99, 0.3);
padding: 3px 8px;
margin: 2px;
border-radius: 4px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 11px;
color: #d1d5db;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 Local Inference UI</h1>
<div class="input-section">
<label for="inputText">Input Text:</label>
<textarea id="inputText" placeholder="Enter your text here..."></textarea>
</div>
<div class="controls">
<div class="control-group">
<label for="maxTokens">Max Tokens:</label>
<input type="number" id="maxTokens" value="150" min="1" max="1000">
</div>
<div class="control-group">
<label for="modelType">Model Type:</label>
<select id="modelType" style="width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; font-size: 14px;">
<option value="summary">Summary</option>
<option value="chat">Text Generation (with Hover)</option>
</select>
</div>
</div>
<button id="generateBtn" onclick="generate()">Generate</button>
<div class="output-section">
<label>Output:</label>
<div class="output-box" id="output">Results will appear here...</div>
</div>
</div>
<script>
const modelToggle = document.getElementById('modelToggle');
const modelType = document.getElementById('modelType');
// Sync toggle with dropdown
modelToggle.addEventListener('change', function() {
modelType.value = this.checked ? 'chat' : 'summary';
updatePlaceholder();
});
modelType.addEventListener('change', function() {
modelToggle.checked = (this.value === 'chat');
updatePlaceholder();
});
function updatePlaceholder() {
const inputText = document.getElementById('inputText');
const modelType = document.getElementById('modelType');
if (modelType.value === 'summary') {
inputText.placeholder = 'Enter a long article or text to summarize...';
} else {
inputText.placeholder = 'Enter your text here for chat generation...';
}
}
// Initialize placeholder on page load
updatePlaceholder();
async function generate() {
const inputText = document.getElementById('inputText').value;
const maxTokens = parseInt(document.getElementById('maxTokens').value);
const output = document.getElementById('output');
const generateBtn = document.getElementById('generateBtn');
const selectedModel = modelType.value;
if (!inputText.trim()) {
output.textContent = 'Please enter some text.';
output.className = 'output-box error';
return;
}
// Show loading state
output.textContent = 'Generating...';
output.className = 'output-box loading';
generateBtn.disabled = true;
try {
const response = await fetch('/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: inputText,
max_new_tokens: maxTokens,
do_sample: false,
model_type: selectedModel
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Display the generated text with interactive words (pass model type)
displayGeneratedText(data, selectedModel);
output.className = 'output-box';
} catch (error) {
output.textContent = `Error: ${error.message}`;
output.className = 'output-box error';
} finally {
generateBtn.disabled = false;
}
}
function displayGeneratedText(data, modelType) {
const output = document.getElementById('output');
const inputText = document.getElementById('inputText').value;
// Clear the output
output.innerHTML = '';
// Add the original prompt text (non-interactive)
const promptSpan = document.createElement('span');
promptSpan.textContent = inputText;
promptSpan.style.color = '#9ca3af';
output.appendChild(promptSpan);
// Check if we have generation details AND we're in chat mode
if (modelType === 'chat' && data.generation_details && data.generation_details.length > 0) {
// Add each generated word as an interactive element with hover tooltips
data.generation_details.forEach((detail, index) => {
const wordSpan = document.createElement('span');
wordSpan.className = 'generated-word';
wordSpan.textContent = detail.chosen;
// Create tooltip with alternatives
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
const header = document.createElement('div');
header.className = 'tooltip-header';
header.textContent = 'Alternative Words:';
tooltip.appendChild(header);
// Add alternative words
detail.alternatives.forEach(alt => {
const altSpan = document.createElement('span');
altSpan.className = 'alternative-word';
altSpan.textContent = alt;
tooltip.appendChild(altSpan);
});
wordSpan.appendChild(tooltip);
output.appendChild(wordSpan);
});
} else {
// For summary mode or fallback - just display the text normally
const generatedSpan = document.createElement('span');
generatedSpan.textContent = data.generated_text;
generatedSpan.style.color = '#e5e7eb';
output.appendChild(generatedSpan);
}
}
// Allow Enter to generate (with Shift+Enter for new line)
document.getElementById('inputText').addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
generate();
}
});
</script>
</body>
</html>