Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -142,27 +142,22 @@ HTML_TEMPLATE = """
|
|
| 142 |
}
|
| 143 |
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
| 144 |
|
| 145 |
-
/* --- FIX 4: PREVENT HORIZONTAL SCROLL --- */
|
| 146 |
body, html {
|
| 147 |
margin: 0; padding: 0; height: 100dvh; width: 100%; max-width: 100%;
|
| 148 |
background: var(--bg); color: var(--text); font-family: 'Outfit', sans-serif;
|
| 149 |
-
overflow: hidden;
|
| 150 |
font-size: 17px;
|
| 151 |
-
|
| 152 |
-
/* --- FIX 2: DISABLE SELECT GLOBALLY --- */
|
| 153 |
-webkit-user-select: none;
|
| 154 |
-moz-user-select: none;
|
| 155 |
-ms-user-select: none;
|
| 156 |
user-select: none;
|
| 157 |
}
|
| 158 |
|
| 159 |
-
/* Allow typing in inputs only */
|
| 160 |
textarea, input {
|
| 161 |
-webkit-user-select: text !important;
|
| 162 |
user-select: text !important;
|
| 163 |
}
|
| 164 |
|
| 165 |
-
/* Make sure content is NOT selectable via long press */
|
| 166 |
.user-content, .ai-content, code, pre, p, h1, h2, span, div {
|
| 167 |
-webkit-user-select: none !important;
|
| 168 |
user-select: none !important;
|
|
@@ -173,7 +168,7 @@ HTML_TEMPLATE = """
|
|
| 173 |
height: 100dvh;
|
| 174 |
width: 100%;
|
| 175 |
position: relative;
|
| 176 |
-
overflow-x: hidden;
|
| 177 |
}
|
| 178 |
|
| 179 |
header {
|
|
@@ -221,11 +216,24 @@ HTML_TEMPLATE = """
|
|
| 221 |
.history-item:last-child { margin-bottom: 0; }
|
| 222 |
.history-item:active { background: #222; color: #fff; border-color: #444; }
|
| 223 |
|
| 224 |
-
.h-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
|
| 225 |
.h-actions { display: none; gap: 15px; }
|
| 226 |
.history-item.active-mode .h-actions { display: flex; }
|
| 227 |
.h-icon { font-size: 16px; color: #fff; padding: 5px; }
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
.brand-section { text-align: center; margin-top: 20px; padding-bottom: env(safe-area-inset-bottom); }
|
| 230 |
.brand-name { font-family: 'Outfit', sans-serif; font-weight: 600; font-size: 12px; color: var(--dim); letter-spacing: 2px; margin-bottom: 10px; opacity: 0.6; }
|
| 231 |
.logout-btn { color: #ef4444; cursor: pointer; font-size: 15px; font-weight: 600; padding: 10px; }
|
|
@@ -277,7 +285,6 @@ HTML_TEMPLATE = """
|
|
| 277 |
.ai-content strong { color: #fff; font-weight: 700; }
|
| 278 |
.ai-content h1, .ai-content h2 { margin-top: 20px; color: #fff; font-weight: 700; }
|
| 279 |
|
| 280 |
-
/* FIX 3: STYLE FOR IMAGES TO BE CLICKABLE */
|
| 281 |
.ai-content img, .user-content img { cursor: pointer; transition: 0.2s; }
|
| 282 |
.ai-content img:active, .user-content img:active { transform: scale(0.98); }
|
| 283 |
|
|
@@ -314,7 +321,6 @@ HTML_TEMPLATE = """
|
|
| 314 |
#login-overlay { position: fixed; inset: 0; background: #000; z-index: 2000; display: flex; align-items: center; justify-content: center; }
|
| 315 |
.login-box { width: 90%; max-width: 350px; text-align: center; padding: 40px; border: 1px solid var(--border); border-radius: 20px; background: #0a0a0a; }
|
| 316 |
|
| 317 |
-
/* --- FIX 3: LIGHTBOX (IMAGE PREVIEW) CSS --- */
|
| 318 |
#image-modal {
|
| 319 |
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
| 320 |
background: rgba(0,0,0,0.9); z-index: 3000;
|
|
@@ -391,7 +397,6 @@ HTML_TEMPLATE = """
|
|
| 391 |
let currentAttachment = { type: null, data: null, name: null };
|
| 392 |
let longPressTimer;
|
| 393 |
|
| 394 |
-
// --- HELPER FOR INTRO TEXT ---
|
| 395 |
function getIntroHtml(name) {
|
| 396 |
return `<div class="msg ai-msg"><div class="ai-content"><h1>Hi ${name},</h1><p>Ready to master your studies today?</p></div></div>`;
|
| 397 |
}
|
|
@@ -422,7 +427,6 @@ HTML_TEMPLATE = """
|
|
| 422 |
if(!currentChatId) {
|
| 423 |
const box = document.getElementById("chat-box");
|
| 424 |
if(box.innerHTML === "") {
|
| 425 |
-
// FIX 1: USE FUNCTION FOR CONSISTENT INTRO
|
| 426 |
box.innerHTML = getIntroHtml(currentUser);
|
| 427 |
}
|
| 428 |
}
|
|
@@ -541,8 +545,7 @@ HTML_TEMPLATE = """
|
|
| 541 |
function editMessage(oldText) { document.getElementById('input').value = oldText; document.getElementById('input').focus(); }
|
| 542 |
function regenerate(text) { document.getElementById('input').value = text; send(); }
|
| 543 |
|
| 544 |
-
// ---
|
| 545 |
-
// Open Image Logic
|
| 546 |
document.getElementById('chat-box').addEventListener('click', function(e) {
|
| 547 |
if(e.target.tagName === 'IMG') {
|
| 548 |
const modal = document.getElementById('image-modal');
|
|
@@ -558,20 +561,57 @@ HTML_TEMPLATE = """
|
|
| 558 |
setTimeout(() => modal.style.display = 'none', 300);
|
| 559 |
}
|
| 560 |
|
| 561 |
-
// --- HISTORY LOGIC ---
|
| 562 |
-
function handleHistoryTouchStart(e, cid
|
| 563 |
longPressTimer = setTimeout(() => {
|
| 564 |
e.target.closest('.history-item').classList.add('active-mode');
|
| 565 |
}, 600);
|
| 566 |
}
|
| 567 |
function handleHistoryTouchEnd(e) { clearTimeout(longPressTimer); }
|
| 568 |
|
| 569 |
-
|
| 570 |
-
const
|
| 571 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
}
|
|
|
|
| 573 |
async function deleteChat(cid) {
|
| 574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
}
|
| 576 |
|
| 577 |
async function loadHistory() {
|
|
@@ -583,11 +623,11 @@ HTML_TEMPLATE = """
|
|
| 583 |
Object.keys(data.chats).reverse().forEach(cid => {
|
| 584 |
const title = data.chats[cid].title || "New Chat";
|
| 585 |
list.innerHTML += `
|
| 586 |
-
<div class="history-item" onclick="loadChat('${cid}')" oncontextmenu="return false;"
|
| 587 |
-
ontouchstart="handleHistoryTouchStart(event, '${cid}'
|
| 588 |
<span class="h-title">${title}</span>
|
| 589 |
<div class="h-actions">
|
| 590 |
-
<i class="fas fa-pen h-icon" onclick="event.stopPropagation();
|
| 591 |
<i class="fas fa-trash h-icon" onclick="event.stopPropagation(); deleteChat('${cid}')"></i>
|
| 592 |
</div>
|
| 593 |
</div>`;
|
|
@@ -615,7 +655,6 @@ HTML_TEMPLATE = """
|
|
| 615 |
function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); }
|
| 616 |
async function newChat() {
|
| 617 |
currentChatId = null;
|
| 618 |
-
// FIX 1: SET INTRO TEXT FOR EVERY NEW CHAT
|
| 619 |
document.getElementById('chat-box').innerHTML = getIntroHtml(currentUser);
|
| 620 |
|
| 621 |
const r = await fetch('/new_chat', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({username:currentUser})});
|
|
@@ -642,6 +681,31 @@ HTML_TEMPLATE = """
|
|
| 642 |
box.scrollTop = box.scrollHeight;
|
| 643 |
}
|
| 644 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
checkLogin();
|
| 646 |
</script>
|
| 647 |
</body>
|
|
@@ -715,7 +779,7 @@ def chat():
|
|
| 715 |
def manifest():
|
| 716 |
data = {
|
| 717 |
"name": "Student's AI",
|
| 718 |
-
"short_name": "
|
| 719 |
"start_url": "/",
|
| 720 |
"display": "standalone",
|
| 721 |
"orientation": "portrait",
|
|
@@ -723,12 +787,12 @@ def manifest():
|
|
| 723 |
"theme_color": "#09090b",
|
| 724 |
"icons": [
|
| 725 |
{
|
| 726 |
-
"src": "https://
|
| 727 |
"sizes": "192x192",
|
| 728 |
"type": "image/png"
|
| 729 |
},
|
| 730 |
{
|
| 731 |
-
"src": "https://
|
| 732 |
"sizes": "512x512",
|
| 733 |
"type": "image/png"
|
| 734 |
}
|
|
@@ -737,4 +801,5 @@ def manifest():
|
|
| 737 |
return Response(json.dumps(data), mimetype='application/json')
|
| 738 |
|
| 739 |
if __name__ == '__main__':
|
| 740 |
-
app.run(host='0.0.0.0', port=7860)
|
|
|
|
|
|
| 142 |
}
|
| 143 |
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
| 144 |
|
|
|
|
| 145 |
body, html {
|
| 146 |
margin: 0; padding: 0; height: 100dvh; width: 100%; max-width: 100%;
|
| 147 |
background: var(--bg); color: var(--text); font-family: 'Outfit', sans-serif;
|
| 148 |
+
overflow: hidden;
|
| 149 |
font-size: 17px;
|
|
|
|
|
|
|
| 150 |
-webkit-user-select: none;
|
| 151 |
-moz-user-select: none;
|
| 152 |
-ms-user-select: none;
|
| 153 |
user-select: none;
|
| 154 |
}
|
| 155 |
|
|
|
|
| 156 |
textarea, input {
|
| 157 |
-webkit-user-select: text !important;
|
| 158 |
user-select: text !important;
|
| 159 |
}
|
| 160 |
|
|
|
|
| 161 |
.user-content, .ai-content, code, pre, p, h1, h2, span, div {
|
| 162 |
-webkit-user-select: none !important;
|
| 163 |
user-select: none !important;
|
|
|
|
| 168 |
height: 100dvh;
|
| 169 |
width: 100%;
|
| 170 |
position: relative;
|
| 171 |
+
overflow-x: hidden;
|
| 172 |
}
|
| 173 |
|
| 174 |
header {
|
|
|
|
| 216 |
.history-item:last-child { margin-bottom: 0; }
|
| 217 |
.history-item:active { background: #222; color: #fff; border-color: #444; }
|
| 218 |
|
| 219 |
+
.h-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; flex: 1; margin-right: 10px; }
|
| 220 |
.h-actions { display: none; gap: 15px; }
|
| 221 |
.history-item.active-mode .h-actions { display: flex; }
|
| 222 |
.h-icon { font-size: 16px; color: #fff; padding: 5px; }
|
| 223 |
|
| 224 |
+
/* --- NEW STYLE FOR INLINE RENAME INPUT --- */
|
| 225 |
+
.rename-input {
|
| 226 |
+
background: transparent;
|
| 227 |
+
border: none;
|
| 228 |
+
border-bottom: 1px solid #fff;
|
| 229 |
+
color: #fff;
|
| 230 |
+
font-family: 'Outfit', sans-serif;
|
| 231 |
+
font-size: 15px;
|
| 232 |
+
width: 100%;
|
| 233 |
+
outline: none;
|
| 234 |
+
padding: 0;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
.brand-section { text-align: center; margin-top: 20px; padding-bottom: env(safe-area-inset-bottom); }
|
| 238 |
.brand-name { font-family: 'Outfit', sans-serif; font-weight: 600; font-size: 12px; color: var(--dim); letter-spacing: 2px; margin-bottom: 10px; opacity: 0.6; }
|
| 239 |
.logout-btn { color: #ef4444; cursor: pointer; font-size: 15px; font-weight: 600; padding: 10px; }
|
|
|
|
| 285 |
.ai-content strong { color: #fff; font-weight: 700; }
|
| 286 |
.ai-content h1, .ai-content h2 { margin-top: 20px; color: #fff; font-weight: 700; }
|
| 287 |
|
|
|
|
| 288 |
.ai-content img, .user-content img { cursor: pointer; transition: 0.2s; }
|
| 289 |
.ai-content img:active, .user-content img:active { transform: scale(0.98); }
|
| 290 |
|
|
|
|
| 321 |
#login-overlay { position: fixed; inset: 0; background: #000; z-index: 2000; display: flex; align-items: center; justify-content: center; }
|
| 322 |
.login-box { width: 90%; max-width: 350px; text-align: center; padding: 40px; border: 1px solid var(--border); border-radius: 20px; background: #0a0a0a; }
|
| 323 |
|
|
|
|
| 324 |
#image-modal {
|
| 325 |
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
| 326 |
background: rgba(0,0,0,0.9); z-index: 3000;
|
|
|
|
| 397 |
let currentAttachment = { type: null, data: null, name: null };
|
| 398 |
let longPressTimer;
|
| 399 |
|
|
|
|
| 400 |
function getIntroHtml(name) {
|
| 401 |
return `<div class="msg ai-msg"><div class="ai-content"><h1>Hi ${name},</h1><p>Ready to master your studies today?</p></div></div>`;
|
| 402 |
}
|
|
|
|
| 427 |
if(!currentChatId) {
|
| 428 |
const box = document.getElementById("chat-box");
|
| 429 |
if(box.innerHTML === "") {
|
|
|
|
| 430 |
box.innerHTML = getIntroHtml(currentUser);
|
| 431 |
}
|
| 432 |
}
|
|
|
|
| 545 |
function editMessage(oldText) { document.getElementById('input').value = oldText; document.getElementById('input').focus(); }
|
| 546 |
function regenerate(text) { document.getElementById('input').value = text; send(); }
|
| 547 |
|
| 548 |
+
// --- LIGHTBOX ---
|
|
|
|
| 549 |
document.getElementById('chat-box').addEventListener('click', function(e) {
|
| 550 |
if(e.target.tagName === 'IMG') {
|
| 551 |
const modal = document.getElementById('image-modal');
|
|
|
|
| 561 |
setTimeout(() => modal.style.display = 'none', 300);
|
| 562 |
}
|
| 563 |
|
| 564 |
+
// --- HISTORY & INLINE RENAME LOGIC ---
|
| 565 |
+
function handleHistoryTouchStart(e, cid) {
|
| 566 |
longPressTimer = setTimeout(() => {
|
| 567 |
e.target.closest('.history-item').classList.add('active-mode');
|
| 568 |
}, 600);
|
| 569 |
}
|
| 570 |
function handleHistoryTouchEnd(e) { clearTimeout(longPressTimer); }
|
| 571 |
|
| 572 |
+
function startRename(cid, currentTitle) {
|
| 573 |
+
const item = document.getElementById('chat-' + cid);
|
| 574 |
+
const titleSpan = item.querySelector('.h-title');
|
| 575 |
+
|
| 576 |
+
// Create input
|
| 577 |
+
const input = document.createElement('input');
|
| 578 |
+
input.type = 'text';
|
| 579 |
+
input.value = currentTitle;
|
| 580 |
+
input.className = 'rename-input';
|
| 581 |
+
|
| 582 |
+
// Handle save on Enter or Blur
|
| 583 |
+
async function save() {
|
| 584 |
+
const newTitle = input.value.trim();
|
| 585 |
+
if(newTitle && newTitle !== currentTitle) {
|
| 586 |
+
await fetch('/rename_chat', {
|
| 587 |
+
method:'POST', headers:{'Content-Type':'application/json'},
|
| 588 |
+
body:JSON.stringify({username:currentUser, chat_id:cid, title:newTitle})
|
| 589 |
+
});
|
| 590 |
+
loadHistory();
|
| 591 |
+
} else {
|
| 592 |
+
// Revert if empty or same
|
| 593 |
+
loadHistory();
|
| 594 |
+
}
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
input.addEventListener('blur', save);
|
| 598 |
+
input.addEventListener('keydown', (e) => {
|
| 599 |
+
if(e.key === 'Enter') { input.blur(); }
|
| 600 |
+
});
|
| 601 |
+
|
| 602 |
+
// Replace span with input
|
| 603 |
+
titleSpan.replaceWith(input);
|
| 604 |
+
input.focus();
|
| 605 |
}
|
| 606 |
+
|
| 607 |
async function deleteChat(cid) {
|
| 608 |
+
// FIX: No confirmation, just delete immediately
|
| 609 |
+
await fetch('/delete_chat', {
|
| 610 |
+
method:'POST', headers:{'Content-Type':'application/json'},
|
| 611 |
+
body:JSON.stringify({username:currentUser, chat_id:cid})
|
| 612 |
+
});
|
| 613 |
+
if(currentChatId === cid) newChat();
|
| 614 |
+
loadHistory();
|
| 615 |
}
|
| 616 |
|
| 617 |
async function loadHistory() {
|
|
|
|
| 623 |
Object.keys(data.chats).reverse().forEach(cid => {
|
| 624 |
const title = data.chats[cid].title || "New Chat";
|
| 625 |
list.innerHTML += `
|
| 626 |
+
<div class="history-item" id="chat-${cid}" onclick="loadChat('${cid}')" oncontextmenu="return false;"
|
| 627 |
+
ontouchstart="handleHistoryTouchStart(event, '${cid}')" ontouchend="handleHistoryTouchEnd(event)">
|
| 628 |
<span class="h-title">${title}</span>
|
| 629 |
<div class="h-actions">
|
| 630 |
+
<i class="fas fa-pen h-icon" onclick="event.stopPropagation(); startRename('${cid}', '${title}')"></i>
|
| 631 |
<i class="fas fa-trash h-icon" onclick="event.stopPropagation(); deleteChat('${cid}')"></i>
|
| 632 |
</div>
|
| 633 |
</div>`;
|
|
|
|
| 655 |
function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); }
|
| 656 |
async function newChat() {
|
| 657 |
currentChatId = null;
|
|
|
|
| 658 |
document.getElementById('chat-box').innerHTML = getIntroHtml(currentUser);
|
| 659 |
|
| 660 |
const r = await fetch('/new_chat', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({username:currentUser})});
|
|
|
|
| 681 |
box.scrollTop = box.scrollHeight;
|
| 682 |
}
|
| 683 |
|
| 684 |
+
@app.route('/manifest.json')
|
| 685 |
+
def manifest():
|
| 686 |
+
data = {
|
| 687 |
+
"name": "Student's AI",
|
| 688 |
+
"short_name": "StudentAI",
|
| 689 |
+
"start_url": "/",
|
| 690 |
+
"display": "standalone",
|
| 691 |
+
"orientation": "portrait",
|
| 692 |
+
"background_color": "#09090b",
|
| 693 |
+
"theme_color": "#09090b",
|
| 694 |
+
"icons": [
|
| 695 |
+
{
|
| 696 |
+
"src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
|
| 697 |
+
"sizes": "192x192",
|
| 698 |
+
"type": "image/png"
|
| 699 |
+
},
|
| 700 |
+
{
|
| 701 |
+
"src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
|
| 702 |
+
"sizes": "512x512",
|
| 703 |
+
"type": "image/png"
|
| 704 |
+
}
|
| 705 |
+
]
|
| 706 |
+
}
|
| 707 |
+
return Response(json.dumps(data), mimetype='application/json')
|
| 708 |
+
|
| 709 |
checkLogin();
|
| 710 |
</script>
|
| 711 |
</body>
|
|
|
|
| 779 |
def manifest():
|
| 780 |
data = {
|
| 781 |
"name": "Student's AI",
|
| 782 |
+
"short_name": "StudentAI",
|
| 783 |
"start_url": "/",
|
| 784 |
"display": "standalone",
|
| 785 |
"orientation": "portrait",
|
|
|
|
| 787 |
"theme_color": "#09090b",
|
| 788 |
"icons": [
|
| 789 |
{
|
| 790 |
+
"src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
|
| 791 |
"sizes": "192x192",
|
| 792 |
"type": "image/png"
|
| 793 |
},
|
| 794 |
{
|
| 795 |
+
"src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
|
| 796 |
"sizes": "512x512",
|
| 797 |
"type": "image/png"
|
| 798 |
}
|
|
|
|
| 801 |
return Response(json.dumps(data), mimetype='application/json')
|
| 802 |
|
| 803 |
if __name__ == '__main__':
|
| 804 |
+
app.run(host='0.0.0.0', port=7860)
|
| 805 |
+
|