Add a button to use the local image generation model that is loaded on the website to generate an image. The prompt for the image is of the most recent message in the users chat with the AI. The image will be displayed in chat as the most recent message.
Browse files- index.html +10 -1
- script.js +105 -5
- style.css +6 -1
index.html
CHANGED
|
@@ -153,8 +153,11 @@
|
|
| 153 |
<button class="p-3 rounded-full bg-surface-light hover:bg-gray-700 transition" title="Export Chat">
|
| 154 |
<i data-feather="download" class="w-5 h-5"></i>
|
| 155 |
</button>
|
|
|
|
|
|
|
|
|
|
| 156 |
</div>
|
| 157 |
-
|
| 158 |
</div>
|
| 159 |
|
| 160 |
<!-- Chat Messages Container -->
|
|
@@ -241,6 +244,12 @@
|
|
| 241 |
<script src="components/image-generator.js"></script>
|
| 242 |
<script src="components/local-model.js"></script>
|
| 243 |
<script src="script.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
<script>
|
| 245 |
feather.replace();
|
| 246 |
|
|
|
|
| 153 |
<button class="p-3 rounded-full bg-surface-light hover:bg-gray-700 transition" title="Export Chat">
|
| 154 |
<i data-feather="download" class="w-5 h-5"></i>
|
| 155 |
</button>
|
| 156 |
+
<button id="generateImageButton" class="p-3 rounded-full bg-surface-light hover:bg-gray-700 transition" title="Generate Image from Last Message">
|
| 157 |
+
<i data-feather="image" class="w-5 h-5"></i>
|
| 158 |
+
</button>
|
| 159 |
</div>
|
| 160 |
+
</div>
|
| 161 |
</div>
|
| 162 |
|
| 163 |
<!-- Chat Messages Container -->
|
|
|
|
| 244 |
<script src="components/image-generator.js"></script>
|
| 245 |
<script src="components/local-model.js"></script>
|
| 246 |
<script src="script.js"></script>
|
| 247 |
+
<script>
|
| 248 |
+
feather.replace();
|
| 249 |
+
|
| 250 |
+
// Initialize image generation button
|
| 251 |
+
document.getElementById('generateImageButton').addEventListener('click', generateImageFromLastMessage);
|
| 252 |
+
</script>
|
| 253 |
<script>
|
| 254 |
feather.replace();
|
| 255 |
|
script.js
CHANGED
|
@@ -15,18 +15,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 15 |
const stopButton = document.getElementById('stopButton');
|
| 16 |
const sendButton = document.getElementById('sendButton');
|
| 17 |
const generateImageBtn = document.getElementById('generateImageBtn');
|
|
|
|
| 18 |
const aiStatus = document.getElementById('aiStatus');
|
| 19 |
const apiStatus = document.getElementById('apiStatus');
|
| 20 |
-
|
| 21 |
messageInput.addEventListener('input', function() {
|
| 22 |
charCount.textContent = `${this.value.length}/1000`;
|
| 23 |
});
|
| 24 |
-
|
| 25 |
// Image generation button
|
| 26 |
if (generateImageBtn) {
|
| 27 |
generateImageBtn.addEventListener('click', generateSceneImage);
|
| 28 |
}
|
| 29 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
chatForm.addEventListener('submit', async function(e) {
|
| 31 |
e.preventDefault();
|
| 32 |
if (isGenerating) {
|
|
@@ -92,7 +96,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 92 |
updateAPIStatus(true, 'Using fallback');
|
| 93 |
}
|
| 94 |
}
|
| 95 |
-
|
| 96 |
// Generate scene image using local model
|
| 97 |
async function generateSceneImage() {
|
| 98 |
if (!LOCAL_IMAGE_MODEL) {
|
|
@@ -132,7 +135,104 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 132 |
generator.shadowRoot.querySelector('.generate-btn').click();
|
| 133 |
}, 500);
|
| 134 |
}
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
async function generateLocalAIResponse(characterName, messages, length) {
|
| 137 |
return new Promise((resolve) => {
|
| 138 |
// Simulate AI thinking time
|
|
|
|
| 15 |
const stopButton = document.getElementById('stopButton');
|
| 16 |
const sendButton = document.getElementById('sendButton');
|
| 17 |
const generateImageBtn = document.getElementById('generateImageBtn');
|
| 18 |
+
const generateImageButton = document.getElementById('generateImageButton');
|
| 19 |
const aiStatus = document.getElementById('aiStatus');
|
| 20 |
const apiStatus = document.getElementById('apiStatus');
|
| 21 |
+
// Character counter
|
| 22 |
messageInput.addEventListener('input', function() {
|
| 23 |
charCount.textContent = `${this.value.length}/1000`;
|
| 24 |
});
|
|
|
|
| 25 |
// Image generation button
|
| 26 |
if (generateImageBtn) {
|
| 27 |
generateImageBtn.addEventListener('click', generateSceneImage);
|
| 28 |
}
|
| 29 |
+
// New image generation from last message
|
| 30 |
+
if (generateImageButton) {
|
| 31 |
+
generateImageButton.addEventListener('click', generateImageFromLastMessage);
|
| 32 |
+
}
|
| 33 |
+
// Send message
|
| 34 |
chatForm.addEventListener('submit', async function(e) {
|
| 35 |
e.preventDefault();
|
| 36 |
if (isGenerating) {
|
|
|
|
| 96 |
updateAPIStatus(true, 'Using fallback');
|
| 97 |
}
|
| 98 |
}
|
|
|
|
| 99 |
// Generate scene image using local model
|
| 100 |
async function generateSceneImage() {
|
| 101 |
if (!LOCAL_IMAGE_MODEL) {
|
|
|
|
| 135 |
generator.shadowRoot.querySelector('.generate-btn').click();
|
| 136 |
}, 500);
|
| 137 |
}
|
| 138 |
+
|
| 139 |
+
// Generate image from the most recent message in chat
|
| 140 |
+
async function generateImageFromLastMessage() {
|
| 141 |
+
if (!LOCAL_IMAGE_MODEL) {
|
| 142 |
+
addMessage('system', 'Local image model is not enabled.');
|
| 143 |
+
return;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// Get the most recent message content
|
| 147 |
+
const messages = chatMessages.querySelectorAll('.message');
|
| 148 |
+
let lastMessage = '';
|
| 149 |
+
|
| 150 |
+
// Look for the last user or AI message
|
| 151 |
+
for (let i = messages.length - 1; i >= 0; i--) {
|
| 152 |
+
const msg = messages[i];
|
| 153 |
+
const p = msg.querySelector('p');
|
| 154 |
+
if (p && !p.textContent.includes('Generating an image')) {
|
| 155 |
+
lastMessage = p.textContent;
|
| 156 |
+
const isUser = msg.classList.contains('user');
|
| 157 |
+
// Use appropriate sender
|
| 158 |
+
const sender = isUser ? 'You' : document.getElementById('characterName').textContent;
|
| 159 |
+
break;
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
if (!lastMessage.trim()) {
|
| 164 |
+
lastMessage = "A mysterious scene from the conversation";
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// Add a message indicating image generation is starting
|
| 168 |
+
const thinkingMsg = addMessage('user', `*Generating image for: "${lastMessage.substring(0, 50)}..."*`);
|
| 169 |
+
|
| 170 |
+
// Show typing indicator
|
| 171 |
+
showTyping();
|
| 172 |
+
setGenerating(true);
|
| 173 |
+
updateAPIStatus(true, 'Generating image...');
|
| 174 |
+
|
| 175 |
+
try {
|
| 176 |
+
// Simulate local image generation (using static.photos based on prompt)
|
| 177 |
+
const prompt = lastMessage.substring(0, 100);
|
| 178 |
+
const categories = ['fantasy', 'nature', 'abstract', 'cityscape', 'technology', 'people'];
|
| 179 |
+
const category = categories[Math.floor(Math.random() * categories.length)];
|
| 180 |
+
const seed = Math.floor(Math.random() * 1000);
|
| 181 |
+
const imageUrl = `https://static.photos/${category}/400x300/${seed}`;
|
| 182 |
+
|
| 183 |
+
// Simulate delay
|
| 184 |
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
| 185 |
+
|
| 186 |
+
removeTyping();
|
| 187 |
+
setGenerating(false);
|
| 188 |
+
|
| 189 |
+
// Remove the thinking message
|
| 190 |
+
if (thinkingMsg && thinkingMsg.parentNode) {
|
| 191 |
+
thinkingMsg.parentNode.remove();
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
// Display the generated image in chat
|
| 195 |
+
displayGeneratedImage(imageUrl, prompt);
|
| 196 |
+
updateAPIStatus(true, 'Image generated');
|
| 197 |
+
} catch (error) {
|
| 198 |
+
removeTyping();
|
| 199 |
+
setGenerating(false);
|
| 200 |
+
addMessage('system', `Image generation failed: ${error.message}`);
|
| 201 |
+
updateAPIStatus(false, 'Image failed');
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
// Display generated image in chat
|
| 206 |
+
function displayGeneratedImage(imageUrl, prompt) {
|
| 207 |
+
const messageDiv = document.createElement('div');
|
| 208 |
+
messageDiv.className = 'message ai animate-message-slide mb-6';
|
| 209 |
+
|
| 210 |
+
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
| 211 |
+
const avatar = document.querySelector('.bg-surface.rounded-xl.p-5 img')?.src || 'https://static.photos/people/48x48/5';
|
| 212 |
+
const characterName = document.getElementById('characterName').textContent;
|
| 213 |
+
|
| 214 |
+
messageDiv.innerHTML = `
|
| 215 |
+
<div class="flex gap-4">
|
| 216 |
+
<img src="${avatar}" class="w-12 h-12 rounded-full flex-shrink-0">
|
| 217 |
+
<div>
|
| 218 |
+
<div class="font-medium text-primary mb-1">${characterName}</div>
|
| 219 |
+
<div class="bg-surface-light rounded-2xl rounded-tl-none p-4">
|
| 220 |
+
<p>Here's an image inspired by our conversation:</p>
|
| 221 |
+
<div class="image-gen-preview mt-3">
|
| 222 |
+
<img src="${imageUrl}" alt="Generated image" class="rounded-lg" style="max-width: 100%; height: auto;">
|
| 223 |
+
</div>
|
| 224 |
+
<p class="text-sm text-gray-400 mt-2">Prompt: "${prompt.substring(0, 80)}..."</p>
|
| 225 |
+
</div>
|
| 226 |
+
<div class="text-xs text-gray-500 mt-2">${time}</div>
|
| 227 |
+
</div>
|
| 228 |
+
</div>
|
| 229 |
+
`;
|
| 230 |
+
|
| 231 |
+
chatMessages.appendChild(messageDiv);
|
| 232 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 233 |
+
feather.replace();
|
| 234 |
+
}
|
| 235 |
+
// Local AI response generation (simulated)
|
| 236 |
async function generateLocalAIResponse(characterName, messages, length) {
|
| 237 |
return new Promise((resolve) => {
|
| 238 |
// Simulate AI thinking time
|
style.css
CHANGED
|
@@ -114,17 +114,22 @@
|
|
| 114 |
align-items: flex-end;
|
| 115 |
}
|
| 116 |
}
|
| 117 |
-
|
| 118 |
/* Image generation preview */
|
| 119 |
.image-gen-preview {
|
| 120 |
margin-top: 1rem;
|
| 121 |
border-radius: 0.75rem;
|
| 122 |
overflow: hidden;
|
| 123 |
border: 2px solid #4b5563;
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
.image-gen-preview img {
|
| 127 |
width: 100%;
|
| 128 |
height: auto;
|
| 129 |
display: block;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
}
|
|
|
|
| 114 |
align-items: flex-end;
|
| 115 |
}
|
| 116 |
}
|
|
|
|
| 117 |
/* Image generation preview */
|
| 118 |
.image-gen-preview {
|
| 119 |
margin-top: 1rem;
|
| 120 |
border-radius: 0.75rem;
|
| 121 |
overflow: hidden;
|
| 122 |
border: 2px solid #4b5563;
|
| 123 |
+
max-width: 400px;
|
| 124 |
}
|
| 125 |
|
| 126 |
.image-gen-preview img {
|
| 127 |
width: 100%;
|
| 128 |
height: auto;
|
| 129 |
display: block;
|
| 130 |
+
transition: transform 0.3s ease;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.image-gen-preview img:hover {
|
| 134 |
+
transform: scale(1.02);
|
| 135 |
}
|