Spaces:
Running
Running
Update index.html
Browse files- index.html +103 -101
index.html
CHANGED
|
@@ -273,9 +273,27 @@
|
|
| 273 |
--accent-glow: 0 0 20px rgba(16, 163, 127, 0.2);
|
| 274 |
}
|
| 275 |
|
| 276 |
-
.
|
| 277 |
-
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
}
|
| 280 |
|
| 281 |
/* Message Entrance Animation */
|
|
@@ -4514,9 +4532,7 @@
|
|
| 4514 |
<div class="input-container-bottom" id="inputBottomRow">
|
| 4515 |
<div class="input-actions-left">
|
| 4516 |
<button class="action-btn" id="uploadBtn" onclick="toggleUploadMenu()">
|
| 4517 |
-
<
|
| 4518 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
| 4519 |
-
</svg>
|
| 4520 |
</button>
|
| 4521 |
<div class="upload-menu" id="uploadMenu">
|
| 4522 |
<div class="tool-btn" data-action="search" title="Web Search">
|
|
@@ -4598,14 +4614,10 @@
|
|
| 4598 |
</button>
|
| 4599 |
<!-- FIXED: Removed inline onclick to prevent double-firing and used consistent SVG icons -->
|
| 4600 |
<button class="send-button" id="sendBtn" title="Send message (Enter)">
|
| 4601 |
-
<
|
| 4602 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
|
| 4603 |
-
</svg>
|
| 4604 |
</button>
|
| 4605 |
<button class="stop-button" id="stopBtn" title="Stop generating (Esc)">
|
| 4606 |
-
<
|
| 4607 |
-
<rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor" />
|
| 4608 |
-
</svg>
|
| 4609 |
</button>
|
| 4610 |
</div>
|
| 4611 |
</div>
|
|
@@ -4809,6 +4821,10 @@
|
|
| 4809 |
<span id="authToggleText">Don't have an account?</span>
|
| 4810 |
<span class="auth-toggle" id="authToggleBtn" onclick="toggleAuthMode()">Sign Up</span>
|
| 4811 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4812 |
</div>
|
| 4813 |
</div>
|
| 4814 |
|
|
@@ -6051,95 +6067,6 @@
|
|
| 6051 |
const thinkingDiv = showThinking();
|
| 6052 |
const messageContent = thinkingDiv.querySelector('.message-content');
|
| 6053 |
|
| 6054 |
-
// Check for video generation intent
|
| 6055 |
-
const videoKeywords = ['generate video', 'create video', 'make a video', 'video about'];
|
| 6056 |
-
const isVideoRequest = videoKeywords.some(k => query.toLowerCase().includes(k));
|
| 6057 |
-
|
| 6058 |
-
if (isVideoRequest) {
|
| 6059 |
-
messageContent.innerHTML = `
|
| 6060 |
-
<div style="display:flex; align-items:center; gap:10px;">
|
| 6061 |
-
<div class="thinking"><span></span><span></span><span></span></div>
|
| 6062 |
-
<span style="font-size:13px; color:var(--text-secondary); font-weight:500;">Imagining your video...</span>
|
| 6063 |
-
</div>
|
| 6064 |
-
`;
|
| 6065 |
-
smoothScrollToBottom();
|
| 6066 |
-
|
| 6067 |
-
try {
|
| 6068 |
-
const response = await fetch('/api/video/generate', {
|
| 6069 |
-
method: 'POST',
|
| 6070 |
-
headers: { 'Content-Type': 'application/json' },
|
| 6071 |
-
body: JSON.stringify({ prompt: query })
|
| 6072 |
-
});
|
| 6073 |
-
const data = await response.json();
|
| 6074 |
-
|
| 6075 |
-
if (data.status === 'success') {
|
| 6076 |
-
hideThinking(thinkingDiv);
|
| 6077 |
-
addMessage(`I've generated a video based on your request: "${query}"`, 'ai');
|
| 6078 |
-
|
| 6079 |
-
const lastMsg = chatHistory.lastElementChild;
|
| 6080 |
-
const msgContent = lastMsg.querySelector('.message-content');
|
| 6081 |
-
const videoCard = document.createElement('div');
|
| 6082 |
-
videoCard.className = 'image-card';
|
| 6083 |
-
const videoId = 'vid-' + Date.now();
|
| 6084 |
-
|
| 6085 |
-
if (data.is_motion_image) {
|
| 6086 |
-
videoCard.innerHTML = `
|
| 6087 |
-
<div class="video-container" style="background:none; position: relative;">
|
| 6088 |
-
<img src="${data.video_url}" alt="${query}" class="motion-image" style="width:100%; border-radius:12px; display:block;">
|
| 6089 |
-
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.5); border-radius: 50%; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; pointer-events: none;">
|
| 6090 |
-
<i data-lucide="play" style="width: 30px; height: 30px; color: white; fill: white;"></i>
|
| 6091 |
-
</div>
|
| 6092 |
-
</div>
|
| 6093 |
-
<div class="image-card-actions">
|
| 6094 |
-
<a href="${data.video_url}" class="img-btn" onclick="downloadImage(event, '${data.video_url}')"><i data-lucide="download"></i> Download</a>
|
| 6095 |
-
<button class="img-btn" onclick="openVideoOverlayModal('${data.video_url}')"><i data-lucide="type"></i> Add Text</button>
|
| 6096 |
-
</div>
|
| 6097 |
-
`;
|
| 6098 |
-
} else {
|
| 6099 |
-
videoCard.innerHTML = `
|
| 6100 |
-
<div class="video-container" id="${videoId}-container">
|
| 6101 |
-
<video id="${videoId}" autoplay loop muted playsinline>
|
| 6102 |
-
<source src="${data.video_url}" type="video/mp4">
|
| 6103 |
-
</video>
|
| 6104 |
-
<div class="custom-video-controls">
|
| 6105 |
-
<button class="vid-ctrl-btn play-pause">
|
| 6106 |
-
<i data-lucide="pause"></i>
|
| 6107 |
-
</button>
|
| 6108 |
-
<div class="video-progress">
|
| 6109 |
-
<div class="video-progress-filled"></div>
|
| 6110 |
-
</div>
|
| 6111 |
-
<div class="volume-container">
|
| 6112 |
-
<button class="vid-ctrl-btn volume-btn">
|
| 6113 |
-
<i data-lucide="volume-x"></i>
|
| 6114 |
-
</button>
|
| 6115 |
-
<div class="volume-slider-vertical">
|
| 6116 |
-
<input type="range" min="0" max="1" step="0.1" value="0">
|
| 6117 |
-
</div>
|
| 6118 |
-
</div>
|
| 6119 |
-
</div>
|
| 6120 |
-
</div>
|
| 6121 |
-
<div class="image-card-actions">
|
| 6122 |
-
<a href="${data.video_url}" download class="img-btn"><i data-lucide="download"></i> Download Video</a>
|
| 6123 |
-
<button class="img-btn" onclick="openVideoOverlayModal('${data.video_url}')"><i data-lucide="type"></i> Add Text</button>
|
| 6124 |
-
</div>
|
| 6125 |
-
`;
|
| 6126 |
-
}
|
| 6127 |
-
msgContent.appendChild(videoCard);
|
| 6128 |
-
if (window.lucide) lucide.createIcons();
|
| 6129 |
-
if (!data.is_motion_image) initCustomVideoPlayer(videoId);
|
| 6130 |
-
smoothScrollToBottom();
|
| 6131 |
-
return;
|
| 6132 |
-
} else {
|
| 6133 |
-
throw new Error(data.error);
|
| 6134 |
-
}
|
| 6135 |
-
} catch (err) {
|
| 6136 |
-
console.error("Video generation failed:", err);
|
| 6137 |
-
hideThinking(thinkingDiv);
|
| 6138 |
-
addMessage(err.message, 'ai');
|
| 6139 |
-
return;
|
| 6140 |
-
}
|
| 6141 |
-
}
|
| 6142 |
-
|
| 6143 |
// Task 15: Stream the AI response normally (backend handles tool detection)
|
| 6144 |
addMessage(query, 'user');
|
| 6145 |
|
|
@@ -6183,6 +6110,12 @@
|
|
| 6183 |
if (!line.trim()) continue;
|
| 6184 |
try {
|
| 6185 |
const data = JSON.parse(line);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6186 |
|
| 6187 |
// Task: Handle search results from backend synthesis
|
| 6188 |
if (data.search_results) {
|
|
@@ -6244,6 +6177,75 @@
|
|
| 6244 |
const badge = (toolsUsed.includes('search')) ? '<div class="compiled-search-badge"><i data-lucide="brain"></i> Synthesized Research Report</div>' : '';
|
| 6245 |
aiContentEl.innerHTML = badge + marked.parse(fullResponse);
|
| 6246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6247 |
// If backend used image tool, call image API and append to message
|
| 6248 |
if (toolsUsed.includes('image')) {
|
| 6249 |
const imageContainer = document.createElement('div');
|
|
|
|
| 273 |
--accent-glow: 0 0 20px rgba(16, 163, 127, 0.2);
|
| 274 |
}
|
| 275 |
|
| 276 |
+
.image-card, .tool-output-card {
|
| 277 |
+
min-height: 100px;
|
| 278 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 279 |
+
contain: content;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.img-skeleton {
|
| 283 |
+
background: linear-gradient(90deg, rgba(255,255,255,0.03) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.03) 75%);
|
| 284 |
+
background-size: 200% 100%;
|
| 285 |
+
animation: skeleton-loading 1.5s infinite;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
@keyframes skeleton-loading {
|
| 289 |
+
0% { background-position: 200% 0; }
|
| 290 |
+
100% { background-position: -200% 0; }
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
/* Fix for layout shift in chat history */
|
| 294 |
+
.message-content {
|
| 295 |
+
min-width: 0;
|
| 296 |
+
overflow-wrap: break-word;
|
| 297 |
}
|
| 298 |
|
| 299 |
/* Message Entrance Animation */
|
|
|
|
| 4532 |
<div class="input-container-bottom" id="inputBottomRow">
|
| 4533 |
<div class="input-actions-left">
|
| 4534 |
<button class="action-btn" id="uploadBtn" onclick="toggleUploadMenu()">
|
| 4535 |
+
<i data-lucide="plus"></i>
|
|
|
|
|
|
|
| 4536 |
</button>
|
| 4537 |
<div class="upload-menu" id="uploadMenu">
|
| 4538 |
<div class="tool-btn" data-action="search" title="Web Search">
|
|
|
|
| 4614 |
</button>
|
| 4615 |
<!-- FIXED: Removed inline onclick to prevent double-firing and used consistent SVG icons -->
|
| 4616 |
<button class="send-button" id="sendBtn" title="Send message (Enter)">
|
| 4617 |
+
<i data-lucide="arrow-up"></i>
|
|
|
|
|
|
|
| 4618 |
</button>
|
| 4619 |
<button class="stop-button" id="stopBtn" title="Stop generating (Esc)">
|
| 4620 |
+
<i data-lucide="square"></i>
|
|
|
|
|
|
|
| 4621 |
</button>
|
| 4622 |
</div>
|
| 4623 |
</div>
|
|
|
|
| 4821 |
<span id="authToggleText">Don't have an account?</span>
|
| 4822 |
<span class="auth-toggle" id="authToggleBtn" onclick="toggleAuthMode()">Sign Up</span>
|
| 4823 |
</div>
|
| 4824 |
+
<div style="margin-top: 15px; text-align: center; font-size: 13px; opacity: 0.7;">
|
| 4825 |
+
<span style="color: var(--text-secondary);">Or </span>
|
| 4826 |
+
<span class="auth-toggle" onclick="loginAsGuest()" style="color: #10a37f; cursor: pointer; font-weight: 600;">Continue as Guest</span>
|
| 4827 |
+
</div>
|
| 4828 |
</div>
|
| 4829 |
</div>
|
| 4830 |
|
|
|
|
| 6067 |
const thinkingDiv = showThinking();
|
| 6068 |
const messageContent = thinkingDiv.querySelector('.message-content');
|
| 6069 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6070 |
// Task 15: Stream the AI response normally (backend handles tool detection)
|
| 6071 |
addMessage(query, 'user');
|
| 6072 |
|
|
|
|
| 6110 |
if (!line.trim()) continue;
|
| 6111 |
try {
|
| 6112 |
const data = JSON.parse(line);
|
| 6113 |
+
|
| 6114 |
+
if (data.error) {
|
| 6115 |
+
hideThinking(thinkingDiv);
|
| 6116 |
+
addMessage(data.error, 'ai');
|
| 6117 |
+
return;
|
| 6118 |
+
}
|
| 6119 |
|
| 6120 |
// Task: Handle search results from backend synthesis
|
| 6121 |
if (data.search_results) {
|
|
|
|
| 6177 |
const badge = (toolsUsed.includes('search')) ? '<div class="compiled-search-badge"><i data-lucide="brain"></i> Synthesized Research Report</div>' : '';
|
| 6178 |
aiContentEl.innerHTML = badge + marked.parse(fullResponse);
|
| 6179 |
|
| 6180 |
+
// If backend used video tool, call video API and append to message
|
| 6181 |
+
if (toolsUsed.includes('video')) {
|
| 6182 |
+
const videoContainer = document.createElement('div');
|
| 6183 |
+
videoContainer.className = 'image-card';
|
| 6184 |
+
videoContainer.innerHTML = `<div class="img-skeleton visible" style="height: 300px; background: rgba(255,255,255,0.05); border-radius: 12px; animation: pulse 1.5s infinite; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 10px;">
|
| 6185 |
+
<i data-lucide="video" class="animate-pulse" style="width:40px; height:40px; color: #10a37f;"></i>
|
| 6186 |
+
<span style="font-size: 14px; color: var(--text-secondary);">Generating AI Video...</span>
|
| 6187 |
+
</div>`;
|
| 6188 |
+
aiContentEl.appendChild(videoContainer);
|
| 6189 |
+
smoothScrollToBottom();
|
| 6190 |
+
if (window.lucide) lucide.createIcons();
|
| 6191 |
+
|
| 6192 |
+
try {
|
| 6193 |
+
const videoRes = await fetch('/api/video/generate', {
|
| 6194 |
+
method: 'POST',
|
| 6195 |
+
headers: {'Content-Type': 'application/json'},
|
| 6196 |
+
body: JSON.stringify({ prompt: query })
|
| 6197 |
+
});
|
| 6198 |
+
const videoData = await videoRes.json();
|
| 6199 |
+
|
| 6200 |
+
if (videoData.status === 'success') {
|
| 6201 |
+
const videoId = 'vid-' + Date.now();
|
| 6202 |
+
if (videoData.is_motion_image) {
|
| 6203 |
+
videoContainer.innerHTML = `
|
| 6204 |
+
<div class="video-container" style="background:none; position: relative;">
|
| 6205 |
+
<img src="${videoData.video_url}" alt="${query}" class="motion-image" style="width:100%; border-radius:12px; display:block;">
|
| 6206 |
+
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.5); border-radius: 50%; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; pointer-events: none;">
|
| 6207 |
+
<i data-lucide="play" style="width: 30px; height: 30px; color: white; fill: white;"></i>
|
| 6208 |
+
</div>
|
| 6209 |
+
</div>
|
| 6210 |
+
<div class="image-card-actions">
|
| 6211 |
+
<a href="${videoData.video_url}" class="img-btn" onclick="downloadImage(event, '${videoData.video_url}')"><i data-lucide="download"></i> Download</a>
|
| 6212 |
+
<button class="img-btn" onclick="openVideoOverlayModal('${videoData.video_url}')"><i data-lucide="type"></i> Add Text</button>
|
| 6213 |
+
</div>
|
| 6214 |
+
`;
|
| 6215 |
+
} else {
|
| 6216 |
+
videoContainer.innerHTML = `
|
| 6217 |
+
<div class="video-container" id="${videoId}-container">
|
| 6218 |
+
<video id="${videoId}" autoplay loop muted playsinline>
|
| 6219 |
+
<source src="${videoData.video_url}" type="video/mp4">
|
| 6220 |
+
</video>
|
| 6221 |
+
<div class="custom-video-controls">
|
| 6222 |
+
<button class="vid-ctrl-btn play-pause"><i data-lucide="pause"></i></button>
|
| 6223 |
+
<div class="video-progress"><div class="video-progress-filled"></div></div>
|
| 6224 |
+
<div class="volume-container">
|
| 6225 |
+
<button class="vid-ctrl-btn volume-btn"><i data-lucide="volume-x"></i></button>
|
| 6226 |
+
<div class="volume-slider-vertical"><input type="range" min="0" max="1" step="0.1" value="0"></div>
|
| 6227 |
+
</div>
|
| 6228 |
+
</div>
|
| 6229 |
+
</div>
|
| 6230 |
+
<div class="image-card-actions">
|
| 6231 |
+
<a href="${videoData.video_url}" download class="img-btn"><i data-lucide="download"></i> Download Video</a>
|
| 6232 |
+
<button class="img-btn" onclick="openVideoOverlayModal('${videoData.video_url}')"><i data-lucide="type"></i> Add Text</button>
|
| 6233 |
+
</div>
|
| 6234 |
+
`;
|
| 6235 |
+
}
|
| 6236 |
+
if (window.lucide) lucide.createIcons();
|
| 6237 |
+
if (!videoData.is_motion_image) initCustomVideoPlayer(videoId);
|
| 6238 |
+
} else {
|
| 6239 |
+
videoContainer.innerHTML = `<div style="padding: 20px; color: #f87171;"><i data-lucide="alert-circle"></i> Video failed: ${videoData.error}</div>`;
|
| 6240 |
+
if (window.lucide) lucide.createIcons();
|
| 6241 |
+
}
|
| 6242 |
+
} catch (vErr) {
|
| 6243 |
+
videoContainer.innerHTML = `<div style="padding: 20px; color: #f87171;"><i data-lucide="alert-circle"></i> Video connection error.</div>`;
|
| 6244 |
+
if (window.lucide) lucide.createIcons();
|
| 6245 |
+
}
|
| 6246 |
+
smoothScrollToBottom();
|
| 6247 |
+
}
|
| 6248 |
+
|
| 6249 |
// If backend used image tool, call image API and append to message
|
| 6250 |
if (toolsUsed.includes('image')) {
|
| 6251 |
const imageContainer = document.createElement('div');
|