| <!DOCTYPE html>
|
| <html lang="ar" dir="rtl">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>المساعد التعليمي الذكي</title>
|
|
|
| <script>
|
| window.MathJax = {
|
| tex: {
|
| inlineMath: [['\\(', '\\)'], ['$', '$']],
|
| displayMath: [['\\[', '\\]'], ['$$', '$$']],
|
| processEscapes: true,
|
| processEnvironments: true,
|
| tags: 'ams'
|
| },
|
| options: {
|
| skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
|
| ignoreHtmlClass: 'no-mathjax'
|
| },
|
| chtml: { mtextInheritFont: true },
|
| startup: {
|
| ready: () => {
|
| MathJax.startup.defaultReady();
|
| MathJax.startup.promise.then(() => { fixMathJaxArabic(); });
|
| }
|
| }
|
| };
|
| function fixMathJaxArabic() {
|
| document.querySelectorAll('mjx-mtext, mjx-container mtext').forEach(el => {
|
| const text = el.textContent || '';
|
| if (/[\u0600-\u06FF]/.test(text)) {
|
| el.style.direction = 'rtl';
|
| el.style.unicodeBidi = 'embed';
|
| el.style.display = 'inline-block';
|
| }
|
| });
|
| }
|
| function fixArabicInMath() {
|
| document.querySelectorAll('mjx-mtext').forEach(el => {
|
| const text = el.textContent || '';
|
| if (/[\u0600-\u06FF]/.test(text)) {
|
| el.style.direction = 'rtl';
|
| el.style.unicodeBidi = 'embed';
|
| el.style.display = 'inline-block';
|
| el.style.fontFamily = 'Tajawal, Arial, sans-serif';
|
| }
|
| });
|
| }
|
| const mathObserver = new MutationObserver((mutations) => {
|
| let hasMathJax = false;
|
| mutations.forEach(mutation => {
|
| mutation.addedNodes.forEach(node => {
|
| if (node.nodeType === 1) {
|
| if (node.tagName && node.tagName.toLowerCase().startsWith('mjx-')) hasMathJax = true;
|
| if (node.querySelector && node.querySelector('mjx-mtext, mjx-container')) hasMathJax = true;
|
| }
|
| });
|
| });
|
| if (hasMathJax) setTimeout(fixArabicInMath, 50);
|
| });
|
| mathObserver.observe(document.body, { childList: true, subtree: true });
|
| </script>
|
| <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" async></script>
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| <link rel="preconnect" href="https://fonts.googleapis.com">
|
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&family=DM+Sans:wght@400;500;700&family=Tajawal:wght@300;400;500;700;800&family=Amiri:wght@400;700&display=swap" rel="stylesheet">
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.min.js"></script>
|
|
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
| <style>
|
| |
| |
|
|
| :root {
|
|
|
| --clay-canvas: #F4F1FA;
|
| --clay-card-bg: rgba(255, 255, 255, 0.72);
|
| --clay-card-solid: #FFFFFF;
|
| --clay-input-bg: #EFEBF5;
|
| --clay-input-focus: #FFFFFF;
|
|
|
|
|
| --clay-fg: #332F3A;
|
| --clay-muted: #635F69;
|
| --clay-subtle: #8B8494;
|
|
|
|
|
| --clay-accent: #7C3AED;
|
| --clay-accent-light: #A78BFA;
|
| --clay-accent-alt: #DB2777;
|
| --clay-sky: #0EA5E9;
|
| --clay-emerald: #10B981;
|
| --clay-amber: #F59E0B;
|
|
|
|
|
| --clay-btn-grad: linear-gradient(135deg, #A78BFA 0%, #7C3AED 100%);
|
| --clay-btn-grad-alt: linear-gradient(135deg, #F472B6 0%, #DB2777 100%);
|
| --clay-header-grad: linear-gradient(135deg, rgba(167,139,250,0.18) 0%, rgba(219,39,119,0.10) 100%);
|
|
|
|
|
|
|
|
|
| --shadow-deep:
|
| 30px 30px 60px #cdc6d9,
|
| -30px -30px 60px #ffffff,
|
| inset 10px 10px 20px rgba(139,92,246,0.05),
|
| inset -10px -10px 20px rgba(255,255,255,0.80);
|
|
|
|
|
| --shadow-card:
|
| 16px 16px 32px rgba(160,150,180,0.22),
|
| -10px -10px 24px rgba(255,255,255,0.92),
|
| inset 6px 6px 12px rgba(139,92,246,0.03),
|
| inset -6px -6px 12px rgba(255,255,255,1);
|
|
|
|
|
| --shadow-card-hover:
|
| 22px 22px 44px rgba(139,92,246,0.18),
|
| -12px -12px 28px rgba(255,255,255,0.95),
|
| inset 6px 6px 12px rgba(139,92,246,0.04),
|
| inset -6px -6px 12px rgba(255,255,255,1);
|
|
|
|
|
| --shadow-btn:
|
| 12px 12px 24px rgba(139,92,246,0.32),
|
| -8px -8px 16px rgba(255,255,255,0.45),
|
| inset 4px 4px 8px rgba(255,255,255,0.42),
|
| inset -4px -4px 8px rgba(0,0,0,0.10);
|
|
|
|
|
| --shadow-btn-hover:
|
| 16px 16px 32px rgba(139,92,246,0.40),
|
| -10px -10px 20px rgba(255,255,255,0.50),
|
| inset 4px 4px 8px rgba(255,255,255,0.45),
|
| inset -4px -4px 8px rgba(0,0,0,0.08);
|
|
|
|
|
| --shadow-pressed:
|
| inset 10px 10px 20px #d9d4e3,
|
| inset -10px -10px 20px #ffffff;
|
|
|
|
|
| --shadow-input:
|
| inset 8px 8px 16px #ddd8e8,
|
| inset -8px -8px 16px #ffffff;
|
|
|
|
|
| --shadow-input-focus:
|
| 16px 16px 32px rgba(160,150,180,0.18),
|
| -10px -10px 24px rgba(255,255,255,0.90),
|
| inset 2px 2px 4px rgba(139,92,246,0.06),
|
| 0 0 0 4px rgba(124,58,237,0.14);
|
|
|
|
|
| --shadow-user-msg:
|
| 10px 10px 20px rgba(139,92,246,0.28),
|
| -6px -6px 14px rgba(255,255,255,0.20),
|
| inset 3px 3px 6px rgba(255,255,255,0.30),
|
| inset -3px -3px 6px rgba(0,0,0,0.12);
|
|
|
|
|
| --shadow-bot-msg:
|
| 10px 10px 20px rgba(160,150,180,0.18),
|
| -6px -6px 14px rgba(255,255,255,0.90),
|
| inset 4px 4px 8px rgba(139,92,246,0.03),
|
| inset -4px -4px 8px rgba(255,255,255,0.95);
|
|
|
|
|
| --shadow-header:
|
| 0 8px 32px rgba(160,150,180,0.18),
|
| 0 2px 8px rgba(139,92,246,0.08);
|
|
|
|
|
| --shadow-status:
|
| 0 -4px 16px rgba(160,150,180,0.12);
|
|
|
|
|
| --shadow-subject:
|
| 12px 12px 24px rgba(160,150,180,0.20),
|
| -8px -8px 18px rgba(255,255,255,0.90),
|
| inset 4px 4px 8px rgba(139,92,246,0.03),
|
| inset -4px -4px 8px rgba(255,255,255,1);
|
|
|
|
|
| --shadow-subject-hover:
|
| 18px 18px 36px rgba(139,92,246,0.22),
|
| -10px -10px 22px rgba(255,255,255,0.95),
|
| inset 4px 4px 8px rgba(139,92,246,0.05),
|
| inset -4px -4px 8px rgba(255,255,255,1);
|
|
|
|
|
| --shadow-subject-selected:
|
| 14px 14px 28px rgba(124,58,237,0.30),
|
| -8px -8px 18px rgba(255,255,255,0.90),
|
| inset 4px 4px 8px rgba(124,58,237,0.08),
|
| inset -4px -4px 8px rgba(255,255,255,0.90),
|
| 0 0 0 3px rgba(124,58,237,0.30);
|
|
|
|
|
| --shadow-modal:
|
| 30px 30px 60px rgba(139,92,246,0.18),
|
| -20px -20px 40px rgba(255,255,255,0.95),
|
| inset 8px 8px 16px rgba(139,92,246,0.04),
|
| inset -8px -8px 16px rgba(255,255,255,1);
|
|
|
|
|
| --shadow-badge:
|
| 6px 6px 12px rgba(160,150,180,0.18),
|
| -4px -4px 8px rgba(255,255,255,0.90),
|
| inset 2px 2px 4px rgba(139,92,246,0.04),
|
| inset -2px -2px 4px rgba(255,255,255,1);
|
|
|
|
|
| --shadow-send:
|
| 10px 10px 20px rgba(139,92,246,0.35),
|
| -6px -6px 14px rgba(255,255,255,0.40),
|
| inset 3px 3px 6px rgba(255,255,255,0.40),
|
| inset -3px -3px 6px rgba(0,0,0,0.12);
|
|
|
|
|
| --shadow-typing:
|
| 10px 10px 20px rgba(160,150,180,0.18),
|
| -6px -6px 14px rgba(255,255,255,0.90),
|
| inset 4px 4px 8px rgba(139,92,246,0.03),
|
| inset -4px -4px 8px rgba(255,255,255,0.95);
|
| }
|
|
|
| |
| |
|
|
| *, *::before, *::after {
|
| margin: 0;
|
| padding: 0;
|
| box-sizing: border-box;
|
| }
|
|
|
| body {
|
| font-family: 'Tajawal', 'DM Sans', sans-serif;
|
| background: var(--clay-canvas);
|
| color: var(--clay-fg);
|
| height: 100vh;
|
| display: flex;
|
| flex-direction: column;
|
| overflow: hidden;
|
|
|
| background-image:
|
| radial-gradient(ellipse 80% 50% at 20% 10%, rgba(167,139,250,0.10) 0%, transparent 60%),
|
| radial-gradient(ellipse 60% 40% at 80% 90%, rgba(219,39,119,0.07) 0%, transparent 55%),
|
| radial-gradient(ellipse 50% 60% at 50% 50%, rgba(14,165,233,0.05) 0%, transparent 70%);
|
| background-attachment: fixed;
|
| }
|
|
|
|
|
| mjx-container {
|
| direction: ltr !important;
|
| }
|
| mjx-container mtext {
|
| unicode-bidi: plaintext !important;
|
| direction: rtl !important;
|
| }
|
|
|
| mjx-container svg text {
|
| direction: rtl !important;
|
| }
|
|
|
| |
| |
|
|
| .clay-blobs {
|
| pointer-events: none;
|
| position: fixed;
|
| inset: 0;
|
| overflow: hidden;
|
| z-index: 0;
|
| }
|
|
|
| .clay-blob {
|
| position: absolute;
|
| border-radius: 50%;
|
| filter: blur(80px);
|
| opacity: 0.55;
|
| }
|
|
|
| .clay-blob-1 {
|
| width: 60vw;
|
| height: 60vw;
|
| max-width: 700px;
|
| max-height: 700px;
|
| background: radial-gradient(circle, rgba(167,139,250,0.35) 0%, rgba(124,58,237,0.12) 70%);
|
| top: -15%;
|
| right: -10%;
|
| animation: clayFloat 10s ease-in-out infinite;
|
| }
|
|
|
| .clay-blob-2 {
|
| width: 50vw;
|
| height: 50vw;
|
| max-width: 580px;
|
| max-height: 580px;
|
| background: radial-gradient(circle, rgba(219,39,119,0.22) 0%, rgba(244,114,182,0.08) 70%);
|
| bottom: -10%;
|
| left: -8%;
|
| animation: clayFloatAlt 12s ease-in-out infinite;
|
| animation-delay: -3s;
|
| }
|
|
|
| .clay-blob-3 {
|
| width: 40vw;
|
| height: 40vw;
|
| max-width: 460px;
|
| max-height: 460px;
|
| background: radial-gradient(circle, rgba(14,165,233,0.18) 0%, rgba(56,189,248,0.06) 70%);
|
| top: 40%;
|
| left: 30%;
|
| animation: clayFloat 14s ease-in-out infinite;
|
| animation-delay: -6s;
|
| }
|
|
|
| .clay-blob-4 {
|
| width: 35vw;
|
| height: 35vw;
|
| max-width: 380px;
|
| max-height: 380px;
|
| background: radial-gradient(circle, rgba(16,185,129,0.14) 0%, rgba(52,211,153,0.05) 70%);
|
| bottom: 20%;
|
| right: 5%;
|
| animation: clayFloatAlt 9s ease-in-out infinite;
|
| animation-delay: -1.5s;
|
| }
|
|
|
| @keyframes clayFloat {
|
| 0%, 100% { transform: translateY(0px) rotate(0deg) scale(1); }
|
| 33% { transform: translateY(-22px) rotate(2deg) scale(1.02); }
|
| 66% { transform: translateY(-10px) rotate(-1deg) scale(0.99); }
|
| }
|
|
|
| @keyframes clayFloatAlt {
|
| 0%, 100% { transform: translateY(0px) rotate(0deg) scale(1); }
|
| 40% { transform: translateY(-16px) rotate(-2.5deg) scale(1.015); }
|
| 70% { transform: translateY(-28px) rotate(1.5deg) scale(1.03); }
|
| }
|
|
|
| @keyframes clayBreathe {
|
| 0%, 100% { transform: scale(1); }
|
| 50% { transform: scale(1.025); }
|
| }
|
|
|
| @keyframes claySpin {
|
| to { transform: rotate(360deg); }
|
| }
|
|
|
| @keyframes clayPulse {
|
| 0%, 100% { opacity: 1; transform: scale(1); }
|
| 50% { opacity: 0.55; transform: scale(0.92); }
|
| }
|
|
|
| @keyframes clayBlink {
|
| 0%, 100% { opacity: 1; }
|
| 50% { opacity: 0; }
|
| }
|
|
|
| @keyframes clayMessageIn {
|
| from { opacity: 0; transform: translateY(14px) scale(0.97); }
|
| to { opacity: 1; transform: translateY(0) scale(1); }
|
| }
|
|
|
| @keyframes clayTyping {
|
| 0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
|
| 30% { transform: translateY(-9px); opacity: 1; }
|
| }
|
|
|
| @media (prefers-reduced-motion: reduce) {
|
| .clay-blob,
|
| .clay-blob-1,
|
| .clay-blob-2,
|
| .clay-blob-3,
|
| .clay-blob-4 { animation: none !important; }
|
| * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
|
| }
|
|
|
| |
| |
|
|
| ::-webkit-scrollbar { width: 7px; height: 7px; }
|
| ::-webkit-scrollbar-track { background: transparent; }
|
| ::-webkit-scrollbar-thumb {
|
| background: rgba(124,58,237,0.22);
|
| border-radius: 20px;
|
| border: 2px solid transparent;
|
| background-clip: padding-box;
|
| }
|
| ::-webkit-scrollbar-thumb:hover { background: rgba(124,58,237,0.40); }
|
|
|
| |
| |
|
|
| .subject-page {
|
| position: relative;
|
| z-index: 1;
|
| display: flex;
|
| flex-direction: column;
|
| align-items: center;
|
| justify-content: center;
|
| min-height: 100vh;
|
| padding: 28px 20px 36px;
|
| overflow-y: auto;
|
| }
|
|
|
| .subject-page.hidden { display: none; }
|
|
|
|
|
| .subject-header {
|
| text-align: center;
|
| margin-bottom: 44px;
|
| }
|
|
|
| .subject-header .main-logo {
|
| font-size: 72px;
|
| margin-bottom: 20px;
|
| display: block;
|
| filter: drop-shadow(0 8px 24px rgba(124,58,237,0.30));
|
| animation: clayBreathe 6s ease-in-out infinite;
|
| color: var(--clay-accent);
|
| }
|
|
|
| .subject-header h1 {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: clamp(26px, 5vw, 40px);
|
| font-weight: 900;
|
| color: var(--clay-fg);
|
| margin-bottom: 12px;
|
| letter-spacing: -0.5px;
|
| line-height: 1.15;
|
| }
|
|
|
| .subject-header h1 span {
|
| background: linear-gradient(135deg, var(--clay-accent) 20%, var(--clay-accent-alt) 80%);
|
| -webkit-background-clip: text;
|
| -webkit-text-fill-color: transparent;
|
| background-clip: text;
|
| }
|
|
|
| .subject-header p {
|
| font-size: 15px;
|
| color: var(--clay-muted);
|
| max-width: 420px;
|
| margin: 0 auto;
|
| line-height: 1.85;
|
| font-weight: 500;
|
| }
|
|
|
|
|
| .subjects-grid {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
| gap: 18px;
|
| max-width: 920px;
|
| width: 100%;
|
| margin-bottom: 36px;
|
| }
|
|
|
|
|
| .subject-card {
|
| background: var(--clay-card-bg);
|
| backdrop-filter: blur(16px);
|
| -webkit-backdrop-filter: blur(16px);
|
| border: 1.5px solid rgba(255,255,255,0.70);
|
| border-radius: 32px;
|
| padding: 28px 20px 24px;
|
| cursor: pointer;
|
| transition: transform 0.35s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.35s ease,
|
| border-color 0.25s ease;
|
| text-align: center;
|
| position: relative;
|
| overflow: hidden;
|
| box-shadow: var(--shadow-subject);
|
| }
|
|
|
|
|
| .subject-card::before {
|
| content: '';
|
| position: absolute;
|
| top: 0; left: 10%; right: 10%;
|
| height: 2px;
|
| background: linear-gradient(90deg, transparent, rgba(167,139,250,0.60), transparent);
|
| border-radius: 0 0 4px 4px;
|
| opacity: 0;
|
| transition: opacity 0.3s ease;
|
| }
|
|
|
| .subject-card:hover {
|
| transform: translateY(-8px) scale(1.02);
|
| box-shadow: var(--shadow-subject-hover);
|
| border-color: rgba(167,139,250,0.55);
|
| }
|
|
|
| .subject-card:hover::before { opacity: 1; }
|
|
|
| .subject-card:active {
|
| transform: translateY(-2px) scale(0.97);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| .subject-card.selected {
|
| border-color: rgba(124,58,237,0.50);
|
| background: rgba(167,139,250,0.14);
|
| box-shadow: var(--shadow-subject-selected);
|
| transform: translateY(-4px);
|
| }
|
|
|
| .subject-card .subject-icon {
|
| font-size: 44px;
|
| margin-bottom: 14px;
|
| display: block;
|
| filter: drop-shadow(0 4px 10px rgba(124,58,237,0.20));
|
| transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
|
| color: var(--clay-accent);
|
| }
|
|
|
| .subject-card:hover .subject-icon {
|
| transform: scale(1.15) rotate(-4deg);
|
| }
|
|
|
| .subject-card .subject-name {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: 16px;
|
| font-weight: 800;
|
| color: var(--clay-fg);
|
| margin-bottom: 6px;
|
| }
|
|
|
| .subject-card .subject-info {
|
| font-size: 12px;
|
| color: var(--clay-muted);
|
| font-weight: 500;
|
| }
|
|
|
| .subject-card .subject-chapters {
|
| display: inline-block;
|
| background: linear-gradient(135deg, rgba(167,139,250,0.22), rgba(124,58,237,0.14));
|
| color: var(--clay-accent);
|
| padding: 4px 12px;
|
| border-radius: 20px;
|
| font-size: 11px;
|
| font-weight: 700;
|
| margin-top: 10px;
|
| border: 1px solid rgba(124,58,237,0.18);
|
| box-shadow: var(--shadow-badge);
|
| }
|
|
|
|
|
| .loading-subjects {
|
| grid-column: 1 / -1;
|
| text-align: center;
|
| color: var(--clay-muted);
|
| padding: 52px 20px;
|
| }
|
|
|
| .loading-subjects .spinner {
|
| width: 44px;
|
| height: 44px;
|
| border: 3px solid rgba(124,58,237,0.15);
|
| border-top-color: var(--clay-accent);
|
| border-radius: 50%;
|
| animation: claySpin 0.85s linear infinite;
|
| margin: 0 auto 18px;
|
| }
|
|
|
| .loading-subjects p {
|
| font-weight: 600;
|
| font-size: 14px;
|
| color: var(--clay-muted);
|
| }
|
|
|
|
|
| .start-btn {
|
| background: var(--clay-btn-grad);
|
| border: none;
|
| color: #fff;
|
| padding: 0 40px;
|
| height: 58px;
|
| border-radius: 24px;
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: 17px;
|
| font-weight: 800;
|
| cursor: pointer;
|
| transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.25s ease,
|
| opacity 0.2s ease;
|
| display: inline-flex;
|
| align-items: center;
|
| gap: 10px;
|
| box-shadow: var(--shadow-btn);
|
| letter-spacing: 0.2px;
|
| }
|
|
|
| .start-btn:hover:not(:disabled) {
|
| transform: translateY(-4px) scale(1.03);
|
| box-shadow: var(--shadow-btn-hover);
|
| }
|
|
|
| .start-btn:active:not(:disabled) {
|
| transform: scale(0.93);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| .start-btn:disabled {
|
| opacity: 0.48;
|
| cursor: not-allowed;
|
| box-shadow: none;
|
| }
|
|
|
|
|
| .no-subjects {
|
| grid-column: 1 / -1;
|
| text-align: center;
|
| padding: 48px 20px;
|
| color: var(--clay-muted);
|
| }
|
|
|
| .no-subjects .no-subjects-icon { font-size: 52px; margin-bottom: 16px; color: var(--clay-muted); }
|
|
|
| .retry-btn {
|
| background: var(--clay-card-bg);
|
| backdrop-filter: blur(12px);
|
| border: 1.5px solid rgba(124,58,237,0.22);
|
| color: var(--clay-accent);
|
| padding: 10px 26px;
|
| border-radius: 20px;
|
| cursor: pointer;
|
| font-family: 'Tajawal', sans-serif;
|
| font-size: 14px;
|
| font-weight: 700;
|
| margin-top: 18px;
|
| transition: all 0.25s ease;
|
| box-shadow: var(--shadow-badge);
|
| display: inline-flex;
|
| align-items: center;
|
| gap: 8px;
|
| }
|
|
|
| .retry-btn:hover {
|
| background: rgba(124,58,237,0.10);
|
| transform: translateY(-2px);
|
| box-shadow: var(--shadow-subject);
|
| }
|
|
|
| |
| |
|
|
| .transition-overlay {
|
| display: none;
|
| position: fixed;
|
| inset: 0;
|
| background: rgba(244,241,250,0.88);
|
| backdrop-filter: blur(20px);
|
| -webkit-backdrop-filter: blur(20px);
|
| z-index: 500;
|
| align-items: center;
|
| justify-content: center;
|
| flex-direction: column;
|
| gap: 20px;
|
| }
|
|
|
| .transition-overlay.active { display: flex; }
|
|
|
| .transition-overlay .spinner {
|
| width: 52px;
|
| height: 52px;
|
| border: 3px solid rgba(124,58,237,0.18);
|
| border-top-color: var(--clay-accent);
|
| border-radius: 50%;
|
| animation: claySpin 0.85s linear infinite;
|
| }
|
|
|
| .transition-overlay p {
|
| color: var(--clay-muted);
|
| font-size: 15px;
|
| font-weight: 600;
|
| }
|
|
|
| |
| |
|
|
| .chat-page {
|
| display: flex;
|
| flex-direction: column;
|
| height: 100vh;
|
| overflow: hidden;
|
| position: relative;
|
| z-index: 1;
|
| }
|
|
|
| .chat-page.hidden { display: none; }
|
|
|
| |
| |
|
|
| .header {
|
| background: rgba(255,255,255,0.75);
|
| backdrop-filter: blur(20px);
|
| -webkit-backdrop-filter: blur(20px);
|
| border-bottom: 1.5px solid rgba(255,255,255,0.80);
|
| padding: 12px 22px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: space-between;
|
| flex-shrink: 0;
|
| z-index: 10;
|
| box-shadow: var(--shadow-header);
|
| border-radius: 0 0 28px 28px;
|
| }
|
|
|
| .header-title {
|
| display: flex;
|
| align-items: center;
|
| gap: 14px;
|
| }
|
|
|
| .header-title .logo {
|
| width: 46px;
|
| height: 46px;
|
| background: var(--clay-btn-grad);
|
| border-radius: 16px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| font-size: 22px;
|
| box-shadow: var(--shadow-btn);
|
| cursor: pointer;
|
| transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.3s ease;
|
| flex-shrink: 0;
|
| color: white;
|
| }
|
|
|
| .header-title .logo:hover {
|
| transform: translateY(-3px) scale(1.08);
|
| box-shadow: var(--shadow-btn-hover);
|
| }
|
|
|
| .header-title .logo:active {
|
| transform: scale(0.92);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| .header-title h1 {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: 17px;
|
| font-weight: 900;
|
| background: linear-gradient(135deg, var(--clay-accent) 20%, var(--clay-accent-alt) 80%);
|
| -webkit-background-clip: text;
|
| -webkit-text-fill-color: transparent;
|
| background-clip: text;
|
| line-height: 1.2;
|
| }
|
|
|
| .header-title .subtitle {
|
| font-size: 11.5px;
|
| color: var(--clay-muted);
|
| font-weight: 500;
|
| margin-top: 1px;
|
| }
|
|
|
| .subject-badge-header {
|
| display: inline-flex;
|
| align-items: center;
|
| gap: 5px;
|
| background: linear-gradient(135deg, rgba(167,139,250,0.18), rgba(124,58,237,0.10));
|
| border: 1px solid rgba(124,58,237,0.22);
|
| color: var(--clay-accent);
|
| padding: 3px 11px;
|
| border-radius: 20px;
|
| font-size: 11px;
|
| font-weight: 700;
|
| margin-top: 4px;
|
| box-shadow: var(--shadow-badge);
|
| }
|
|
|
| .header-actions {
|
| display: flex;
|
| gap: 8px;
|
| align-items: center;
|
| }
|
|
|
| .btn-header {
|
| background: rgba(255,255,255,0.70);
|
| backdrop-filter: blur(8px);
|
| border: 1.5px solid rgba(124,58,237,0.16);
|
| color: var(--clay-muted);
|
| padding: 8px 14px;
|
| border-radius: 16px;
|
| cursor: pointer;
|
| font-family: 'Tajawal', sans-serif;
|
| font-size: 13px;
|
| font-weight: 600;
|
| transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.25s ease,
|
| background 0.2s ease,
|
| color 0.2s ease;
|
| display: flex;
|
| align-items: center;
|
| gap: 6px;
|
| box-shadow: var(--shadow-badge);
|
| }
|
|
|
| .btn-header:hover {
|
| background: rgba(255,255,255,0.92);
|
| border-color: rgba(124,58,237,0.35);
|
| color: var(--clay-accent);
|
| transform: translateY(-2px);
|
| box-shadow: var(--shadow-subject);
|
| }
|
|
|
| .btn-header:active {
|
| transform: scale(0.94);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| .btn-change-subject {
|
| background: linear-gradient(135deg, rgba(167,139,250,0.18), rgba(124,58,237,0.10));
|
| border-color: rgba(124,58,237,0.28);
|
| color: var(--clay-accent);
|
| }
|
|
|
| .btn-change-subject:hover {
|
| background: linear-gradient(135deg, rgba(167,139,250,0.30), rgba(124,58,237,0.18));
|
| }
|
|
|
| .session-badge {
|
| background: linear-gradient(135deg, rgba(16,185,129,0.14), rgba(52,211,153,0.08));
|
| border: 1px solid rgba(16,185,129,0.25);
|
| color: #059669;
|
| padding: 6px 12px;
|
| border-radius: 20px;
|
| font-size: 11px;
|
| font-weight: 700;
|
| display: flex;
|
| align-items: center;
|
| gap: 5px;
|
| box-shadow: var(--shadow-badge);
|
| }
|
|
|
| .session-badge .dot {
|
| width: 7px;
|
| height: 7px;
|
| background: #10B981;
|
| border-radius: 50%;
|
| animation: clayPulse 2.2s ease-in-out infinite;
|
| flex-shrink: 0;
|
| }
|
|
|
| |
| |
|
|
| .chat-container {
|
| flex: 1;
|
| overflow-y: auto;
|
| padding: 24px 20px;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 18px;
|
| scroll-behavior: smooth;
|
| }
|
|
|
| |
| |
|
|
| .welcome-message {
|
| text-align: center;
|
| padding: 48px 24px 36px;
|
| color: var(--clay-muted);
|
| max-width: 560px;
|
| margin: 0 auto;
|
| }
|
|
|
| .welcome-message .welcome-icon {
|
| font-size: 64px;
|
| margin-bottom: 20px;
|
| display: block;
|
| filter: drop-shadow(0 8px 20px rgba(124,58,237,0.25));
|
| animation: clayBreathe 5s ease-in-out infinite;
|
| color: var(--clay-accent);
|
| }
|
|
|
| .welcome-message h2 {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| color: var(--clay-fg);
|
| font-size: 24px;
|
| font-weight: 900;
|
| margin-bottom: 10px;
|
| }
|
|
|
| .welcome-message p {
|
| font-size: 14.5px;
|
| line-height: 1.85;
|
| max-width: 460px;
|
| margin: 0 auto;
|
| color: var(--clay-muted);
|
| font-weight: 500;
|
| }
|
|
|
| .quick-actions {
|
| display: flex;
|
| flex-wrap: wrap;
|
| gap: 10px;
|
| justify-content: center;
|
| margin-top: 24px;
|
| }
|
|
|
| .quick-action {
|
| background: rgba(255,255,255,0.72);
|
| backdrop-filter: blur(12px);
|
| border: 1.5px solid rgba(124,58,237,0.18);
|
| color: var(--clay-accent);
|
| padding: 9px 18px;
|
| border-radius: 20px;
|
| cursor: pointer;
|
| font-family: 'Tajawal', sans-serif;
|
| font-size: 13px;
|
| font-weight: 700;
|
| transition: transform 0.28s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.28s ease,
|
| background 0.2s ease;
|
| box-shadow: var(--shadow-badge);
|
| display: flex;
|
| align-items: center;
|
| gap: 6px;
|
| }
|
|
|
| .quick-action:hover {
|
| background: linear-gradient(135deg, rgba(167,139,250,0.22), rgba(124,58,237,0.12));
|
| border-color: rgba(124,58,237,0.38);
|
| transform: translateY(-3px) scale(1.04);
|
| box-shadow: var(--shadow-subject);
|
| }
|
|
|
| .quick-action:active {
|
| transform: scale(0.94);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| |
| |
|
|
| .message {
|
| display: flex;
|
| gap: 10px;
|
| max-width: 86%;
|
| animation: clayMessageIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
|
| }
|
|
|
| .message.user {
|
| align-self: flex-start;
|
| flex-direction: row;
|
| }
|
|
|
| .message.bot {
|
| align-self: flex-end;
|
| flex-direction: row-reverse;
|
| }
|
|
|
| .message-avatar {
|
| width: 38px;
|
| height: 38px;
|
| border-radius: 14px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| font-size: 17px;
|
| flex-shrink: 0;
|
| margin-top: 4px;
|
| }
|
|
|
| .message.user .message-avatar {
|
| background: var(--clay-btn-grad);
|
| box-shadow: var(--shadow-btn);
|
| color: white;
|
| }
|
|
|
| .message.bot .message-avatar {
|
| background: rgba(255,255,255,0.80);
|
| border: 1.5px solid rgba(124,58,237,0.18);
|
| box-shadow: var(--shadow-badge);
|
| color: var(--clay-accent);
|
| }
|
|
|
| .message-wrapper {
|
| min-width: 0;
|
| flex: 1;
|
| }
|
|
|
| .message-content {
|
| padding: 14px 20px;
|
| border-radius: 24px;
|
| line-height: 1.85;
|
| font-size: 14.5px;
|
| position: relative;
|
| word-wrap: break-word;
|
| overflow-wrap: break-word;
|
| font-weight: 500;
|
| }
|
|
|
|
|
| .message.user .message-content {
|
| background: var(--clay-btn-grad);
|
| color: #fff;
|
| border-bottom-right-radius: 6px;
|
| box-shadow: var(--shadow-user-msg);
|
| }
|
|
|
|
|
| .message.bot .message-content {
|
| background: rgba(255,255,255,0.78);
|
| backdrop-filter: blur(16px);
|
| -webkit-backdrop-filter: blur(16px);
|
| color: var(--clay-fg);
|
| border: 1.5px solid rgba(255,255,255,0.85);
|
| border-bottom-left-radius: 6px;
|
| box-shadow: var(--shadow-bot-msg);
|
| }
|
|
|
| .message-meta {
|
| display: flex;
|
| align-items: center;
|
| gap: 8px;
|
| margin-top: 7px;
|
| font-size: 11px;
|
| color: var(--clay-subtle);
|
| font-weight: 500;
|
| }
|
|
|
| .message.user .message-meta {
|
| color: rgba(124,58,237,0.55);
|
| }
|
|
|
| .file-badge {
|
| background: linear-gradient(135deg, rgba(167,139,250,0.20), rgba(124,58,237,0.12));
|
| color: var(--clay-accent);
|
| padding: 2px 9px;
|
| border-radius: 10px;
|
| font-size: 10px;
|
| font-weight: 700;
|
| border: 1px solid rgba(124,58,237,0.18);
|
| }
|
|
|
| |
| |
|
|
| .streaming-cursor {
|
| display: inline-block;
|
| width: 2.5px;
|
| height: 1em;
|
| background: var(--clay-accent);
|
| margin-right: 2px;
|
| vertical-align: text-bottom;
|
| border-radius: 2px;
|
| animation: clayBlink 0.75s ease-in-out infinite;
|
| }
|
|
|
| |
| |
|
|
| .message.bot .message-content h1,
|
| .message.bot .message-content h2,
|
| .message.bot .message-content h3,
|
| .message.bot .message-content h4,
|
| .message.bot .message-content h5,
|
| .message.bot .message-content h6 {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| color: var(--clay-fg);
|
| margin: 18px 0 9px;
|
| line-height: 1.25;
|
| }
|
|
|
| .message.bot .message-content h1 {
|
| font-size: 20px;
|
| font-weight: 900;
|
| border-bottom: 2.5px solid rgba(124,58,237,0.25);
|
| padding-bottom: 8px;
|
| margin-bottom: 14px;
|
| background: linear-gradient(135deg, var(--clay-accent), var(--clay-accent-alt));
|
| -webkit-background-clip: text;
|
| -webkit-text-fill-color: transparent;
|
| background-clip: text;
|
| }
|
|
|
| .message.bot .message-content h2 {
|
| font-size: 17px;
|
| font-weight: 800;
|
| border-bottom: 1.5px solid rgba(124,58,237,0.14);
|
| padding-bottom: 6px;
|
| color: var(--clay-accent);
|
| }
|
|
|
| .message.bot .message-content h3 {
|
| font-size: 15.5px;
|
| font-weight: 800;
|
| color: #6D28D9;
|
| }
|
|
|
| .message.bot .message-content h4 {
|
| font-size: 14.5px;
|
| font-weight: 700;
|
| color: #7C3AED;
|
| }
|
|
|
| .message.bot .message-content h5,
|
| .message.bot .message-content h6 {
|
| font-size: 13.5px;
|
| font-weight: 700;
|
| color: var(--clay-muted);
|
| }
|
|
|
| .message.bot .message-content > h1:first-child,
|
| .message.bot .message-content > h2:first-child,
|
| .message.bot .message-content > h3:first-child { margin-top: 0; }
|
|
|
| .message.bot .message-content p {
|
| margin: 9px 0;
|
| line-height: 1.9;
|
| color: var(--clay-fg);
|
| }
|
| .message.bot .message-content strong,
|
| .message.bot .message-content b {
|
| color: var(--clay-accent);
|
| font-weight: 700;
|
| }
|
|
|
| .message.bot .message-content em,
|
| .message.bot .message-content i {
|
| color: var(--clay-accent-alt);
|
| font-style: italic;
|
| }
|
|
|
| .message.bot .message-content a {
|
| color: var(--clay-sky);
|
| text-decoration: none;
|
| border-bottom: 1.5px dashed rgba(14,165,233,0.45);
|
| transition: all 0.2s ease;
|
| font-weight: 600;
|
| }
|
|
|
| .message.bot .message-content a:hover {
|
| color: var(--clay-accent);
|
| border-bottom-style: solid;
|
| border-bottom-color: var(--clay-accent);
|
| }
|
|
|
| .message.bot .message-content ul,
|
| .message.bot .message-content ol {
|
| margin: 10px 0;
|
| padding-right: 26px;
|
| }
|
|
|
| .message.bot .message-content li {
|
| margin: 7px 0;
|
| line-height: 1.85;
|
| color: var(--clay-fg);
|
| }
|
|
|
| .message.bot .message-content ul li::marker {
|
| color: var(--clay-accent);
|
| }
|
|
|
| .message.bot .message-content ol li::marker {
|
| color: var(--clay-sky);
|
| font-weight: 700;
|
| }
|
|
|
|
|
| .message.bot .message-content .table-wrapper {
|
| overflow-x: auto;
|
| margin: 16px 0;
|
| border-radius: 20px;
|
| border: 1.5px solid rgba(124,58,237,0.14);
|
| box-shadow: var(--shadow-card);
|
| }
|
|
|
| .message.bot .message-content table {
|
| width: 100%;
|
| border-collapse: collapse;
|
| font-size: 13.5px;
|
| min-width: 300px;
|
| background: rgba(255,255,255,0.60);
|
| }
|
|
|
| .message.bot .message-content thead {
|
| background: linear-gradient(135deg, rgba(167,139,250,0.18), rgba(124,58,237,0.10));
|
| }
|
|
|
| .message.bot .message-content th {
|
| padding: 11px 16px;
|
| text-align: right;
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-weight: 800;
|
| color: var(--clay-accent);
|
| border-bottom: 2px solid rgba(124,58,237,0.20);
|
| white-space: nowrap;
|
| font-size: 13px;
|
| }
|
|
|
| .message.bot .message-content td {
|
| padding: 10px 16px;
|
| text-align: right;
|
| border-bottom: 1px solid rgba(124,58,237,0.07);
|
| vertical-align: top;
|
| color: var(--clay-fg);
|
| font-weight: 500;
|
| }
|
|
|
| .message.bot .message-content tbody tr:nth-child(odd) {
|
| background: rgba(167,139,250,0.04);
|
| }
|
|
|
| .message.bot .message-content tbody tr:nth-child(even) {
|
| background: rgba(255,255,255,0.50);
|
| }
|
|
|
| .message.bot .message-content tbody tr:hover {
|
| background: rgba(167,139,250,0.10);
|
| }
|
|
|
| .message.bot .message-content tbody tr:last-child td {
|
| border-bottom: none;
|
| }
|
|
|
|
|
| .message.bot .message-content code:not(pre code) {
|
| background: rgba(124,58,237,0.10);
|
| padding: 2px 8px;
|
| border-radius: 8px;
|
| font-size: 13px;
|
| font-family: 'Courier New', monospace;
|
| color: var(--clay-accent-alt);
|
| border: 1px solid rgba(124,58,237,0.16);
|
| font-weight: 600;
|
| }
|
|
|
|
|
| .message.bot .message-content pre {
|
| background: #1e1a2e;
|
| border: 1.5px solid rgba(124,58,237,0.22);
|
| border-radius: 20px;
|
| padding: 0;
|
| margin: 14px 0;
|
| overflow: hidden;
|
| box-shadow:
|
| 10px 10px 20px rgba(124,58,237,0.15),
|
| -6px -6px 14px rgba(255,255,255,0.60),
|
| inset 0 1px 0 rgba(255,255,255,0.08);
|
| }
|
|
|
| .message.bot .message-content pre .code-header {
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| padding: 9px 16px;
|
| background: rgba(255,255,255,0.06);
|
| border-bottom: 1px solid rgba(124,58,237,0.18);
|
| font-size: 12px;
|
| color: rgba(167,139,250,0.80);
|
| font-weight: 600;
|
| }
|
|
|
| .message.bot .message-content pre .code-header .copy-btn {
|
| background: rgba(167,139,250,0.18);
|
| border: 1px solid rgba(167,139,250,0.30);
|
| color: rgba(167,139,250,0.90);
|
| padding: 4px 12px;
|
| border-radius: 10px;
|
| cursor: pointer;
|
| font-size: 11px;
|
| font-family: 'Tajawal', sans-serif;
|
| font-weight: 700;
|
| transition: all 0.2s ease;
|
| }
|
|
|
| .message.bot .message-content pre .code-header .copy-btn:hover {
|
| background: rgba(167,139,250,0.32);
|
| color: #fff;
|
| transform: translateY(-1px);
|
| }
|
|
|
| .message.bot .message-content pre .code-header .copy-btn:active {
|
| transform: scale(0.94);
|
| }
|
|
|
| .message.bot .message-content pre code {
|
| display: block;
|
| padding: 16px 20px;
|
| overflow-x: auto;
|
| font-size: 13px;
|
| line-height: 1.65;
|
| font-family: 'Courier New', monospace;
|
| color: #e2d9f3;
|
| background: transparent;
|
| border: none;
|
| }
|
|
|
|
|
| .message.bot .message-content blockquote {
|
| border-right: 4px solid var(--clay-accent);
|
| background: linear-gradient(135deg, rgba(167,139,250,0.10), rgba(124,58,237,0.05));
|
| padding: 13px 20px;
|
| margin: 14px 0;
|
| border-radius: 0 16px 16px 0;
|
| color: var(--clay-muted);
|
| font-style: italic;
|
| font-weight: 500;
|
| box-shadow:
|
| 6px 6px 12px rgba(124,58,237,0.08),
|
| -4px -4px 8px rgba(255,255,255,0.80);
|
| }
|
|
|
|
|
| .message.bot .message-content hr {
|
| border: none;
|
| height: 2px;
|
| background: linear-gradient(90deg, transparent, rgba(124,58,237,0.20), transparent);
|
| margin: 18px 0;
|
| border-radius: 2px;
|
| }
|
|
|
|
|
| .message.bot .message-content .MathJax { font-size: 105% !important; }
|
|
|
| .message.bot .message-content mjx-container {
|
| overflow-x: auto;
|
| overflow-y: hidden;
|
| padding: 2px 0;
|
| }
|
|
|
| .message.bot .message-content mjx-container[display="true"] {
|
| margin: 14px 0;
|
| padding: 14px 18px;
|
| background: rgba(255,255,255,0.65);
|
| backdrop-filter: blur(8px);
|
| border-radius: 16px;
|
| border: 1.5px solid rgba(124,58,237,0.12);
|
| box-shadow: var(--shadow-card);
|
| }
|
|
|
| |
| |
|
|
| .typing-indicator {
|
| display: none;
|
| align-self: flex-end;
|
| gap: 10px;
|
| flex-direction: row-reverse;
|
| max-width: 85%;
|
| }
|
|
|
| .typing-indicator.active { display: flex; }
|
|
|
| .typing-dots {
|
| display: flex;
|
| gap: 5px;
|
| padding: 16px 22px;
|
| background: rgba(255,255,255,0.78);
|
| backdrop-filter: blur(16px);
|
| -webkit-backdrop-filter: blur(16px);
|
| border: 1.5px solid rgba(255,255,255,0.85);
|
| border-radius: 24px;
|
| border-bottom-left-radius: 6px;
|
| box-shadow: var(--shadow-typing);
|
| align-items: center;
|
| }
|
|
|
| .typing-dots span {
|
| width: 9px;
|
| height: 9px;
|
| background: linear-gradient(135deg, var(--clay-accent-light), var(--clay-accent));
|
| border-radius: 50%;
|
| animation: clayTyping 1.4s ease-in-out infinite;
|
| box-shadow: 0 2px 6px rgba(124,58,237,0.30);
|
| }
|
|
|
| .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
|
| .typing-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
|
| |
| |
|
|
| .status-bar {
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| gap: 18px;
|
| padding: 7px 16px;
|
| background: rgba(255,255,255,0.65);
|
| backdrop-filter: blur(12px);
|
| -webkit-backdrop-filter: blur(12px);
|
| font-size: 11px;
|
| color: var(--clay-subtle);
|
| flex-shrink: 0;
|
| border-top: 1.5px solid rgba(255,255,255,0.75);
|
| box-shadow: var(--shadow-status);
|
| font-weight: 600;
|
| }
|
|
|
| .status-item {
|
| display: flex;
|
| align-items: center;
|
| gap: 5px;
|
| background: rgba(255,255,255,0.60);
|
| padding: 3px 10px;
|
| border-radius: 12px;
|
| border: 1px solid rgba(124,58,237,0.10);
|
| box-shadow: var(--shadow-badge);
|
| transition: transform 0.2s ease;
|
| }
|
|
|
| .status-item:hover {
|
| transform: translateY(-1px);
|
| border-color: rgba(124,58,237,0.22);
|
| }
|
|
|
| |
| |
|
|
| .input-area {
|
| padding: 14px 20px 18px;
|
| background: rgba(255,255,255,0.72);
|
| backdrop-filter: blur(20px);
|
| -webkit-backdrop-filter: blur(20px);
|
| border-top: 1.5px solid rgba(255,255,255,0.80);
|
| flex-shrink: 0;
|
| border-radius: 28px 28px 0 0;
|
| box-shadow: var(--shadow-header);
|
| }
|
|
|
| .input-wrapper {
|
| display: flex;
|
| gap: 10px;
|
| align-items: flex-end;
|
| max-width: 900px;
|
| margin: 0 auto;
|
| }
|
|
|
| .input-field {
|
| flex: 1;
|
| background: var(--clay-input-bg);
|
| border: 1.5px solid rgba(124,58,237,0.14);
|
| border-radius: 20px;
|
| padding: 13px 20px;
|
| color: var(--clay-fg);
|
| font-family: 'Tajawal', sans-serif;
|
| font-size: 15px;
|
| font-weight: 500;
|
| resize: none;
|
| max-height: 120px;
|
| min-height: 50px;
|
| outline: none;
|
| transition: box-shadow 0.25s ease,
|
| background 0.25s ease,
|
| border-color 0.25s ease;
|
| line-height: 1.55;
|
| box-shadow: var(--shadow-input);
|
| }
|
|
|
| .input-field:focus {
|
| background: var(--clay-input-focus);
|
| border-color: rgba(124,58,237,0.35);
|
| box-shadow: var(--shadow-input-focus);
|
| }
|
|
|
| .input-field::placeholder {
|
| color: var(--clay-subtle);
|
| font-weight: 400;
|
| }
|
|
|
| .send-btn {
|
| width: 50px;
|
| height: 50px;
|
| background: var(--clay-btn-grad);
|
| border: none;
|
| border-radius: 18px;
|
| color: white;
|
| font-size: 20px;
|
| cursor: pointer;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| transition: transform 0.28s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.28s ease,
|
| opacity 0.2s ease;
|
| flex-shrink: 0;
|
| box-shadow: var(--shadow-send);
|
| }
|
|
|
| .send-btn:hover:not(:disabled) {
|
| transform: translateY(-3px) scale(1.08);
|
| box-shadow: var(--shadow-btn-hover);
|
| }
|
|
|
| .send-btn:active:not(:disabled) {
|
| transform: scale(0.92);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| .send-btn:disabled {
|
| opacity: 0.48;
|
| cursor: not-allowed;
|
| box-shadow: none;
|
| }
|
|
|
| |
| |
|
|
| .modal-overlay {
|
| display: none;
|
| position: fixed;
|
| inset: 0;
|
| background: rgba(100,80,140,0.25);
|
| backdrop-filter: blur(12px);
|
| -webkit-backdrop-filter: blur(12px);
|
| z-index: 1000;
|
| align-items: center;
|
| justify-content: center;
|
| padding: 20px;
|
| }
|
|
|
| .modal-overlay.active { display: flex; }
|
|
|
| .modal {
|
| background: rgba(255,255,255,0.88);
|
| backdrop-filter: blur(24px);
|
| -webkit-backdrop-filter: blur(24px);
|
| border: 1.5px solid rgba(255,255,255,0.90);
|
| border-radius: 36px;
|
| padding: 30px;
|
| max-width: 620px;
|
| width: 100%;
|
| max-height: 78vh;
|
| overflow-y: auto;
|
| box-shadow: var(--shadow-modal);
|
| animation: clayMessageIn 0.35s cubic-bezier(0.34,1,1.56,0.64,1);
|
| }
|
|
|
| .modal::-webkit-scrollbar { width: 5px; }
|
| .modal::-webkit-scrollbar-thumb {
|
| background: rgba(124,58,237,0.22);
|
| border-radius: 10px;
|
| }
|
|
|
| .modal h3 {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: 19px;
|
| font-weight: 900;
|
| color: var(--clay-fg);
|
| margin-bottom: 18px;
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| padding-bottom: 14px;
|
| border-bottom: 2px solid rgba(124,58,237,0.12);
|
| }
|
|
|
| .modal-content {
|
| font-size: 14px;
|
| line-height: 1.85;
|
| color: var(--clay-muted);
|
| font-weight: 500;
|
| }
|
|
|
|
|
| .modal-content h1,
|
| .modal-content h2,
|
| .modal-content h3,
|
| .modal-content h4 {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| color: var(--clay-fg);
|
| margin: 14px 0 8px;
|
| font-weight: 800;
|
| }
|
|
|
| .modal-content h1 { font-size: 18px; color: var(--clay-accent); }
|
| .modal-content h2 { font-size: 16px; color: var(--clay-accent); }
|
| .modal-content h3 { font-size: 15px; color: #6D28D9; }
|
| .modal-content p { margin: 8px 0; color: var(--clay-fg); }
|
|
|
| .modal-content strong,
|
| .modal-content b { color: var(--clay-accent); font-weight: 700; }
|
|
|
| .modal-content ul,
|
| .modal-content ol { padding-right: 22px; margin: 8px 0; }
|
|
|
| .modal-content li { margin: 5px 0; color: var(--clay-fg); }
|
| .modal-content ul li::marker { color: var(--clay-accent); }
|
| .modal-content ol li::marker { color: var(--clay-sky); font-weight: 700; }
|
|
|
| .modal-content code:not(pre code) {
|
| background: rgba(124,58,237,0.10);
|
| padding: 2px 7px;
|
| border-radius: 7px;
|
| font-size: 12.5px;
|
| font-family: 'Courier New', monospace;
|
| color: var(--clay-accent-alt);
|
| border: 1px solid rgba(124,58,237,0.15);
|
| }
|
|
|
| .modal-content blockquote {
|
| border-right: 4px solid var(--clay-accent);
|
| background: rgba(167,139,250,0.08);
|
| padding: 10px 16px;
|
| margin: 10px 0;
|
| border-radius: 0 12px 12px 0;
|
| color: var(--clay-muted);
|
| font-style: italic;
|
| }
|
|
|
| .modal-content hr {
|
| border: none;
|
| height: 2px;
|
| background: linear-gradient(90deg, transparent, rgba(124,58,237,0.18), transparent);
|
| margin: 14px 0;
|
| }
|
|
|
| .modal-content mjx-container[display="true"] {
|
| margin: 12px 0;
|
| padding: 12px 16px;
|
| background: rgba(167,139,250,0.07);
|
| border-radius: 14px;
|
| border: 1px solid rgba(124,58,237,0.12);
|
| }
|
|
|
| .modal-close {
|
| background: var(--clay-btn-grad);
|
| border: none;
|
| color: white;
|
| padding: 0 24px;
|
| height: 48px;
|
| border-radius: 18px;
|
| cursor: pointer;
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: 15px;
|
| font-weight: 800;
|
| margin-top: 20px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| width: 100%;
|
| transition: transform 0.28s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.28s ease;
|
| box-shadow: var(--shadow-btn);
|
| }
|
|
|
| .modal-close:hover {
|
| transform: translateY(-3px);
|
| box-shadow: var(--shadow-btn-hover);
|
| }
|
|
|
| .modal-close:active {
|
| transform: scale(0.93);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| |
| |
|
|
| @media (max-width: 768px) {
|
| .subject-header h1 { font-size: 22px; }
|
| .subject-header .main-logo { font-size: 56px; }
|
| .subjects-grid {
|
| grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
|
| gap: 13px;
|
| }
|
| .subject-card { padding: 20px 14px 18px; border-radius: 24px; }
|
| .subject-card .subject-icon { font-size: 36px; }
|
|
|
| .header { padding: 10px 14px; border-radius: 0 0 20px 20px; }
|
| .header-title h1 { font-size: 14px; }
|
| .header-title .logo { width: 40px; height: 40px; font-size: 19px; border-radius: 13px; }
|
| .header-actions { gap: 5px; }
|
| .btn-header { padding: 7px 10px; font-size: 12px; border-radius: 13px; }
|
| .btn-header .btn-text { display: none; }
|
|
|
| .chat-container { padding: 14px 12px; gap: 14px; }
|
| .message { max-width: 94%; }
|
| .message-content { padding: 12px 15px; font-size: 14px; border-radius: 20px; }
|
|
|
| .input-area { padding: 10px 14px 14px; border-radius: 22px 22px 0 0; }
|
| .input-field { font-size: 14px; padding: 11px 16px; border-radius: 17px; }
|
| .send-btn { width: 46px; height: 46px; border-radius: 15px; font-size: 18px; }
|
|
|
| .status-bar { gap: 8px; font-size: 10px; padding: 5px 10px; }
|
| .status-item { padding: 2px 8px; border-radius: 10px; }
|
|
|
| .modal { border-radius: 28px; padding: 22px; }
|
| .modal h3 { font-size: 16px; }
|
|
|
| .welcome-message { padding: 32px 16px 24px; }
|
| .welcome-message .welcome-icon { font-size: 52px; }
|
| .welcome-message h2 { font-size: 20px; }
|
| }
|
|
|
| @media (max-width: 480px) {
|
| .session-badge { display: none; }
|
| .subjects-grid { grid-template-columns: repeat(2, 1fr); gap: 11px; }
|
| .subject-header .main-logo { font-size: 48px; }
|
| .subject-header h1 { font-size: 20px; }
|
| .start-btn { height: 52px; font-size: 15px; padding: 0 28px; border-radius: 20px; }
|
| .message { max-width: 97%; }
|
| .quick-actions { gap: 7px; }
|
| .quick-action { padding: 8px 14px; font-size: 12px; }
|
| }
|
|
|
| |
| |
|
|
| #messageCount,
|
| #totalMessages {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-weight: 800;
|
| }
|
|
|
| |
| |
|
|
| .media-btn {
|
| width: 50px;
|
| height: 50px;
|
| background: rgba(255,255,255,0.72);
|
| border: 1.5px solid rgba(124,58,237,0.18);
|
| border-radius: 18px;
|
| font-size: 20px;
|
| cursor: pointer;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| flex-shrink: 0;
|
| transition: transform 0.28s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.28s ease,
|
| background 0.2s ease,
|
| border-color 0.2s ease;
|
| box-shadow: var(--shadow-badge);
|
| color: var(--clay-accent);
|
| }
|
|
|
| .media-btn:hover:not(:disabled) {
|
| background: rgba(255,255,255,0.92);
|
| border-color: rgba(124,58,237,0.35);
|
| transform: translateY(-3px) scale(1.08);
|
| box-shadow: var(--shadow-subject);
|
| }
|
|
|
| .media-btn:active:not(:disabled) {
|
| transform: scale(0.92);
|
| box-shadow: var(--shadow-pressed);
|
| }
|
|
|
| .media-btn:disabled {
|
| opacity: 0.45;
|
| cursor: not-allowed;
|
| transform: none;
|
| box-shadow: none;
|
| }
|
|
|
|
|
| .media-btn.voice-btn.recording {
|
| background: linear-gradient(135deg, rgba(239,68,68,0.15), rgba(220,38,38,0.08));
|
| border-color: rgba(239,68,68,0.45);
|
| animation: voicePulse 1.2s ease-in-out infinite;
|
| box-shadow:
|
| 0 0 0 0 rgba(239,68,68,0.4),
|
| var(--shadow-badge);
|
| color: #ef4444;
|
| }
|
|
|
| @keyframes voicePulse {
|
| 0%, 100% {
|
| box-shadow:
|
| 0 0 0 0 rgba(239,68,68,0.35),
|
| var(--shadow-badge);
|
| }
|
| 50% {
|
| box-shadow:
|
| 0 0 0 8px rgba(239,68,68,0.0),
|
| var(--shadow-badge);
|
| }
|
| }
|
|
|
| |
| |
|
|
| .image-preview-bar {
|
| max-width: 900px;
|
| margin: 0 auto 10px;
|
| background: rgba(255,255,255,0.82);
|
| backdrop-filter: blur(16px);
|
| -webkit-backdrop-filter: blur(16px);
|
| border: 1.5px solid rgba(124,58,237,0.18);
|
| border-radius: 20px;
|
| padding: 10px 14px;
|
| box-shadow: var(--shadow-card);
|
| animation: clayMessageIn 0.3s cubic-bezier(0.34,1.56,0.64,1);
|
| }
|
|
|
| .image-preview-inner {
|
| display: flex;
|
| align-items: center;
|
| gap: 12px;
|
| }
|
|
|
| #imagePreviewThumb {
|
| width: 52px;
|
| height: 52px;
|
| object-fit: cover;
|
| border-radius: 12px;
|
| border: 1.5px solid rgba(124,58,237,0.18);
|
| box-shadow: var(--shadow-badge);
|
| flex-shrink: 0;
|
| }
|
|
|
| .image-preview-info {
|
| flex: 1;
|
| min-width: 0;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 4px;
|
| }
|
|
|
| #imagePreviewName {
|
| font-size: 13px;
|
| font-weight: 700;
|
| color: var(--clay-fg);
|
| white-space: nowrap;
|
| overflow: hidden;
|
| text-overflow: ellipsis;
|
| }
|
|
|
| .image-preview-status {
|
| font-size: 11.5px;
|
| font-weight: 600;
|
| color: var(--clay-muted);
|
| transition: color 0.2s ease;
|
| }
|
|
|
| .image-preview-status.status-analyzing {
|
| color: var(--clay-amber);
|
| animation: clayPulse 1.5s ease-in-out infinite;
|
| }
|
|
|
| .image-preview-status.status-ready {
|
| color: var(--clay-emerald);
|
| }
|
|
|
| .image-preview-cancel {
|
| width: 30px;
|
| height: 30px;
|
| background: rgba(239,68,68,0.10);
|
| border: 1.5px solid rgba(239,68,68,0.22);
|
| border-radius: 10px;
|
| color: #ef4444;
|
| font-size: 13px;
|
| cursor: pointer;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| flex-shrink: 0;
|
| transition: all 0.2s ease;
|
| font-weight: 700;
|
| }
|
|
|
| .image-preview-cancel:hover {
|
| background: rgba(239,68,68,0.18);
|
| border-color: rgba(239,68,68,0.40);
|
| transform: scale(1.08);
|
| }
|
|
|
| .image-preview-cancel:active {
|
| transform: scale(0.92);
|
| }
|
|
|
| |
| |
|
|
| .message-image-wrapper {
|
| margin-bottom: 8px;
|
| }
|
|
|
| .message-image {
|
| max-width: 260px;
|
| max-height: 200px;
|
| width: 100%;
|
| object-fit: cover;
|
| border-radius: 16px;
|
| border: 1.5px solid rgba(255,255,255,0.40);
|
| box-shadow: var(--shadow-badge);
|
| cursor: pointer;
|
| transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.25s ease;
|
| display: block;
|
| }
|
|
|
| .message-image:hover {
|
| transform: scale(1.03);
|
| box-shadow: var(--shadow-card-hover);
|
| }
|
|
|
| |
| |
|
|
| .voice-recording-ui {
|
| max-width: 900px;
|
| margin: 0 auto 10px;
|
| background: linear-gradient(135deg, rgba(239,68,68,0.08), rgba(220,38,38,0.04));
|
| backdrop-filter: blur(16px);
|
| -webkit-backdrop-filter: blur(16px);
|
| border: 1.5px solid rgba(239,68,68,0.25);
|
| border-radius: 20px;
|
| padding: 12px 16px;
|
| box-shadow: var(--shadow-card);
|
| animation: clayMessageIn 0.3s cubic-bezier(0.34,1.56,0.64,1);
|
| }
|
|
|
| .voice-recording-inner {
|
| display: flex;
|
| align-items: center;
|
| gap: 12px;
|
| flex-wrap: wrap;
|
| }
|
|
|
|
|
| .voice-wave {
|
| display: flex;
|
| align-items: center;
|
| gap: 3px;
|
| height: 28px;
|
| flex-shrink: 0;
|
| }
|
|
|
| .voice-wave span {
|
| display: block;
|
| width: 4px;
|
| border-radius: 4px;
|
| background: linear-gradient(180deg, #ef4444, #dc2626);
|
| animation: voiceWave 0.9s ease-in-out infinite;
|
| }
|
|
|
| .voice-wave span:nth-child(1) { height: 10px; animation-delay: 0s; }
|
| .voice-wave span:nth-child(2) { height: 22px; animation-delay: 0.15s; }
|
| .voice-wave span:nth-child(3) { height: 16px; animation-delay: 0.3s; }
|
| .voice-wave span:nth-child(4) { height: 26px; animation-delay: 0.45s; }
|
| .voice-wave span:nth-child(5) { height: 12px; animation-delay: 0.6s; }
|
|
|
| @keyframes voiceWave {
|
| 0%, 100% { transform: scaleY(1); opacity: 0.7; }
|
| 50% { transform: scaleY(1.6); opacity: 1; }
|
| }
|
|
|
| .voice-timer {
|
| font-family: 'Nunito', 'Tajawal', sans-serif;
|
| font-size: 15px;
|
| font-weight: 800;
|
| color: #ef4444;
|
| flex-shrink: 0;
|
| min-width: 44px;
|
| }
|
|
|
| .voice-label {
|
| font-size: 12.5px;
|
| font-weight: 600;
|
| color: var(--clay-muted);
|
| flex: 1;
|
| }
|
|
|
| .voice-stop-btn {
|
| background: linear-gradient(135deg, #ef4444, #dc2626);
|
| border: none;
|
| color: white;
|
| padding: 7px 16px;
|
| border-radius: 14px;
|
| cursor: pointer;
|
| font-family: 'Tajawal', sans-serif;
|
| font-size: 12.5px;
|
| font-weight: 700;
|
| transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1),
|
| box-shadow 0.25s ease;
|
| box-shadow:
|
| 6px 6px 12px rgba(239,68,68,0.30),
|
| -4px -4px 8px rgba(255,255,255,0.40);
|
| flex-shrink: 0;
|
| }
|
|
|
| .voice-stop-btn:hover {
|
| transform: translateY(-2px) scale(1.04);
|
| box-shadow:
|
| 8px 8px 16px rgba(239,68,68,0.38),
|
| -5px -5px 10px rgba(255,255,255,0.45);
|
| }
|
|
|
| .voice-stop-btn:active {
|
| transform: scale(0.93);
|
| }
|
|
|
| .voice-cancel-btn {
|
| background: rgba(255,255,255,0.70);
|
| border: 1.5px solid rgba(239,68,68,0.22);
|
| color: #ef4444;
|
| padding: 7px 14px;
|
| border-radius: 14px;
|
| cursor: pointer;
|
| font-family: 'Tajawal', sans-serif;
|
| font-size: 12.5px;
|
| font-weight: 700;
|
| transition: all 0.2s ease;
|
| flex-shrink: 0;
|
| }
|
|
|
| .voice-cancel-btn:hover {
|
| background: rgba(239,68,68,0.08);
|
| border-color: rgba(239,68,68,0.40);
|
| transform: translateY(-1px);
|
| }
|
|
|
| .voice-cancel-btn:active {
|
| transform: scale(0.93);
|
| }
|
|
|
| |
| |
|
|
| .unsupported-image-notif {
|
| align-self: center;
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| background: linear-gradient(135deg, rgba(239,68,68,0.12), rgba(220,38,38,0.06));
|
| border: 1.5px solid rgba(239,68,68,0.30);
|
| border-radius: 20px;
|
| padding: 12px 20px;
|
| font-size: 13.5px;
|
| font-weight: 600;
|
| color: #dc2626;
|
| max-width: 86%;
|
| box-shadow:
|
| 8px 8px 16px rgba(239,68,68,0.12),
|
| -6px -6px 12px rgba(255,255,255,0.80);
|
| animation: clayMessageIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
|
| backdrop-filter: blur(12px);
|
| -webkit-backdrop-filter: blur(12px);
|
| }
|
|
|
| .unsupported-icon {
|
| font-size: 20px;
|
| flex-shrink: 0;
|
| color: #dc2626;
|
| }
|
|
|
| |
| |
|
|
| .toast {
|
| position: fixed;
|
| bottom: 100px;
|
| left: 50%;
|
| transform: translateX(-50%) translateY(20px);
|
| background: rgba(255,255,255,0.90);
|
| backdrop-filter: blur(20px);
|
| -webkit-backdrop-filter: blur(20px);
|
| border: 1.5px solid rgba(124,58,237,0.18);
|
| border-radius: 20px;
|
| padding: 12px 24px;
|
| font-size: 13.5px;
|
| font-weight: 700;
|
| color: var(--clay-fg);
|
| box-shadow: var(--shadow-modal);
|
| z-index: 2000;
|
| opacity: 0;
|
| pointer-events: none;
|
| transition: opacity 0.3s ease,
|
| transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
|
| white-space: nowrap;
|
| max-width: 90vw;
|
| text-align: center;
|
| font-family: 'Tajawal', sans-serif;
|
| display: flex;
|
| align-items: center;
|
| gap: 8px;
|
| }
|
|
|
| .toast.show {
|
| opacity: 1;
|
| transform: translateX(-50%) translateY(0);
|
| pointer-events: auto;
|
| }
|
|
|
| .toast.toast-success {
|
| border-color: rgba(16,185,129,0.35);
|
| background: linear-gradient(135deg, rgba(16,185,129,0.12), rgba(255,255,255,0.90));
|
| color: #065f46;
|
| }
|
|
|
| .toast.toast-error {
|
| border-color: rgba(239,68,68,0.35);
|
| background: linear-gradient(135deg, rgba(239,68,68,0.10), rgba(255,255,255,0.90));
|
| color: #991b1b;
|
| }
|
|
|
| .toast.toast-info {
|
| border-color: rgba(14,165,233,0.30);
|
| background: linear-gradient(135deg, rgba(14,165,233,0.10), rgba(255,255,255,0.90));
|
| color: #0c4a6e;
|
| }
|
|
|
| |
| |
|
|
| @media (max-width: 768px) {
|
| .media-btn {
|
| width: 46px;
|
| height: 46px;
|
| font-size: 18px;
|
| border-radius: 15px;
|
| }
|
|
|
| .voice-recording-inner { gap: 8px; }
|
| .voice-label { display: none; }
|
| .voice-stop-btn { padding: 7px 12px; font-size: 12px; }
|
| .voice-cancel-btn { padding: 7px 10px; font-size: 12px; }
|
|
|
| .image-preview-bar { border-radius: 16px; padding: 8px 12px; }
|
| #imagePreviewThumb { width: 44px; height: 44px; border-radius: 10px; }
|
|
|
| .message-image { max-width: 200px; max-height: 160px; }
|
|
|
| .toast { font-size: 12.5px; padding: 10px 18px; bottom: 80px; }
|
|
|
| .unsupported-image-notif { font-size: 12.5px; padding: 10px 16px; }
|
| }
|
|
|
| @media (max-width: 480px) {
|
| .media-btn {
|
| width: 42px;
|
| height: 42px;
|
| font-size: 17px;
|
| border-radius: 13px;
|
| }
|
|
|
| .voice-recording-inner { gap: 6px; }
|
| .voice-stop-btn { padding: 6px 10px; font-size: 11.5px; border-radius: 12px; }
|
| .voice-cancel-btn { padding: 6px 8px; font-size: 11.5px; border-radius: 12px; }
|
|
|
| .voice-wave span { width: 3px; }
|
|
|
| .image-preview-bar { border-radius: 14px; }
|
| #imagePreviewThumb { width: 38px; height: 38px; }
|
| #imagePreviewName { font-size: 12px; }
|
| .image-preview-status { font-size: 10.5px; }
|
|
|
| .message-image { max-width: 170px; max-height: 130px; border-radius: 13px; }
|
|
|
| .toast { font-size: 12px; padding: 9px 16px; border-radius: 16px; bottom: 70px; }
|
|
|
| .unsupported-image-notif { font-size: 12px; padding: 9px 14px; border-radius: 16px; }
|
| .unsupported-icon { font-size: 17px; }
|
| }
|
| </style>
|
| </head>
|
| <body>
|
|
|
|
|
| <div class="clay-blobs" aria-hidden="true">
|
| <div class="clay-blob clay-blob-1"></div>
|
| <div class="clay-blob clay-blob-2"></div>
|
| <div class="clay-blob clay-blob-3"></div>
|
| <div class="clay-blob clay-blob-4"></div>
|
| </div>
|
|
|
|
|
|
|
|
|
| <div class="subject-page hidden" id="subjectPage">
|
| <div class="subject-header">
|
| <div class="main-logo"><i class="fas fa-graduation-cap"></i></div>
|
| <h1>المساعد <span>التعليمي الذكي</span></h1>
|
| <div class="user-info-bar" id="userInfoBar">
|
| <span class="user-greeting"><strong id="userDisplayName">-</strong></span>
|
| <span class="user-track-badge" id="userTrackBadge">-</span>
|
| </div>
|
| <p>اختر المادة التي تريد الدراسة فيها وسيساعدك المساعد الذكي في فهم جميع المواضيع</p>
|
| </div>
|
| <div class="subjects-grid" id="subjectsGrid">
|
| <div class="loading-subjects">
|
| <div class="spinner"></div>
|
| <p>جاري تحميل المواد...</p>
|
| </div>
|
| </div>
|
| <button class="start-btn" id="startBtn" onclick="startChat()" disabled>
|
| <span><i class="fas fa-play"></i></span>
|
| <span>ابدأ المحادثة</span>
|
| </button>
|
| </div>
|
|
|
|
|
| <div class="transition-overlay" id="transitionOverlay">
|
| <div class="spinner"></div>
|
| <p id="transitionText">جاري تحميل المادة...</p>
|
| </div>
|
|
|
|
|
| <div class="chat-page hidden" id="chatPage">
|
|
|
|
|
| <div class="header">
|
| <div class="header-title">
|
| <div class="logo"><i class="fas fa-graduation-cap"></i></div>
|
| <div>
|
| <h1 id="chatTitle">المساعد التعليمي الذكي</h1>
|
| <div class="subtitle" id="chatSubtitle">نظام تعليمي ذكي بالذكاء الاصطناعي</div>
|
| <div class="subject-badge-header" id="subjectBadgeHeader">
|
| <i class="fas fa-book"></i> <span id="currentSubjectName">-</span>
|
| </div>
|
| </div>
|
| </div>
|
| <div class="header-actions">
|
| <div class="user-chip" id="headerUserChip">
|
| <span><i class="fas fa-user"></i></span>
|
| <span id="headerUsername">-</span>
|
| </div>
|
| <div class="session-badge">
|
| <span class="dot"></span>
|
| <span id="messageCount">0 رسائل</span>
|
| </div>
|
| <button class="btn-header" onclick="showSummary()"><i class="fas fa-file-alt"></i> <span class="btn-text">الملخص</span></button>
|
| <button class="btn-header" onclick="clearChat()"><i class="fas fa-trash-alt"></i> <span class="btn-text">مسح</span></button>
|
| <a href="/dashboard" class="btn-header"><i class="fas fa-home"></i> <span class="btn-text">الرئيسية</span></a>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="chat-container" id="chatContainer">
|
| <div class="welcome-message" id="welcomeMessage">
|
| <div class="welcome-icon" id="welcomeIcon"><i class="fas fa-book-open"></i></div>
|
| <h2 id="welcomeTitle">مرحباً بك!</h2>
|
| <p id="welcomeText">اطرح سؤالك وسأجيبك بالتفصيل مع المعادلات والشروحات!</p>
|
| <div class="quick-actions" id="quickActions">
|
| <button class="quick-action" onclick="sendQuick('مرحبا، عرفني بنفسك')"><i class="fas fa-hand-wave"></i> تعرف عليّ</button>
|
| <button class="quick-action" onclick="sendQuick('ما هي المواضيع المتاحة؟')"><i class="fas fa-list"></i> المواضيع</button>
|
| <button class="quick-action" onclick="sendQuick('اشرح لي الفصل الأول')"><i class="fas fa-book-open"></i> الفصل الأول</button>
|
| </div>
|
| </div>
|
| <div class="history-loading" id="historyLoading" style="display:none;">
|
| <div class="spinner"></div>
|
| <p>جاري تحميل سجل المحادثة...</p>
|
| </div>
|
| <div class="typing-indicator" id="typingIndicator">
|
| <div class="message-avatar" style="background:rgba(255,255,255,0.80);border:1.5px solid rgba(124,58,237,0.18);"><i class="fas fa-user-graduate"></i></div>
|
| <div class="typing-dots"><span></span><span></span><span></span></div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="status-bar">
|
| <div class="status-item"><span><i class="fas fa-file-alt"></i></span><span id="currentFile">-</span></div>
|
| <div class="status-item"><span><i class="fas fa-book"></i></span><span id="currentSubjectStatus">-</span></div>
|
| <div class="status-item"><span><i class="fas fa-brain"></i></span><span id="summaryStatus">لا يوجد ملخص</span></div>
|
| <div class="status-item"><span><i class="fas fa-comment-dots"></i></span><span id="totalMessages">0</span></div>
|
| </div>
|
|
|
|
|
| <div class="input-area">
|
|
|
| <div class="image-preview-bar" id="imagePreviewBar" style="display:none;">
|
| <div class="image-preview-inner">
|
| <img id="imagePreviewThumb" src="" alt="preview" />
|
| <div class="image-preview-info">
|
| <span id="imagePreviewName">صورة</span>
|
| <span class="image-preview-status" id="imagePreviewStatus">جاري التحليل...</span>
|
| </div>
|
| <button class="image-preview-cancel" onclick="cancelImage()" title="إلغاء"><i class="fas fa-times"></i></button>
|
| </div>
|
| </div>
|
|
|
| <div class="input-wrapper">
|
| <input type="file" id="imageFileInput" accept="image/*" style="display:none;" onchange="handleImageSelected(event)" />
|
| <button class="media-btn image-btn" id="imageBtn" onclick="document.getElementById('imageFileInput').click()" title="إرسال صورة">
|
| <i class="fas fa-image"></i>
|
| </button>
|
| <button class="media-btn voice-btn" id="voiceBtn" onclick="toggleVoiceRecording()" title="تسجيل صوتي">
|
| <i class="fas fa-microphone"></i>
|
| </button>
|
| <textarea
|
| class="input-field"
|
| id="messageInput"
|
| placeholder="اكتب سؤالك هنا"
|
| rows="1"
|
| onkeydown="handleKeyDown(event)"
|
| oninput="autoResize(this)"
|
| ></textarea>
|
| <button class="send-btn" id="sendBtn" onclick="sendMessage()"><i class="fas fa-paper-plane"></i></button>
|
| </div>
|
|
|
|
|
| <div class="voice-recording-ui" id="voiceRecordingUI" style="display:none;">
|
| <div class="voice-recording-inner">
|
| <div class="voice-wave">
|
| <span></span><span></span><span></span><span></span><span></span>
|
| </div>
|
| <span class="voice-timer" id="voiceTimer">00:00</span>
|
| <span class="voice-label">جاري التسجيل...</span>
|
| <button class="voice-stop-btn" onclick="stopVoiceRecording()"><i class="fas fa-stop-circle"></i> إيقاف وإرسال</button>
|
| <button class="voice-cancel-btn" onclick="cancelVoiceRecording()"><i class="fas fa-times"></i> إلغاء</button>
|
| </div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="modal-overlay" id="summaryModal">
|
| <div class="modal">
|
| <h3><i class="fas fa-file-alt"></i> ملخص المحادثة وتحليل مستوى الطالب</h3>
|
| <div class="modal-content" id="summaryContent">جاري تحميل الملخص...</div>
|
| <button class="modal-close" onclick="closeSummaryModal()">إغلاق</button>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="toast" id="toast"></div>
|
|
|
| <script>
|
|
|
|
|
|
|
| const USERNAME = "{{ username }}";
|
| const SESSION_ID = "{{ session['session_id'] }}";
|
|
|
|
|
|
|
|
|
| let selectedSubject = null;
|
| let selectedSubjectId = null;
|
| let selectedSubjectName = null;
|
| let isProcessing = false;
|
| let messageCounter = 0;
|
| let currentUserData = null;
|
| let historyLoaded = false;
|
|
|
|
|
| let mediaRecorder = null;
|
| let audioChunks = [];
|
| let voiceTimerInterval = null;
|
| let voiceSeconds = 0;
|
| let isRecording = false;
|
|
|
|
|
| let pendingImageUrl = null;
|
| let pendingImageExtracted = null;
|
| let pendingImageFile = null;
|
| let imageAnalyzing = false;
|
|
|
|
|
| const subjectIconMap = {
|
| 'islamic': 'fa-book-quran',
|
| 'english': 'fa-language',
|
| 'math': 'fa-calculator',
|
| 'it': 'fa-laptop-code',
|
| 'statistics': 'fa-chart-pie',
|
| 'chemistry': 'fa-flask',
|
| 'physics_electric': 'fa-bolt',
|
| 'physics_mechanic': 'fa-cogs',
|
| 'biology': 'fa-dna',
|
| 'literary_studies': 'fa-feather',
|
| 'linguistic_studies': 'fa-pen-nib',
|
| 'literature': 'fa-book',
|
| 'philosophy': 'fa-brain',
|
| 'reading_writing': 'fa-pen',
|
| 'grammar': 'fa-spell-check',
|
| 'criticism': 'fa-comments',
|
| 'history': 'fa-landmark',
|
| 'geography': 'fa-globe',
|
| 'psychology': 'fa-head-side-virus',
|
| 'national_education': 'fa-flag',
|
| 'default': 'fa-book'
|
| };
|
|
|
| function getSubjectIcon(subjectId) {
|
| return subjectIconMap[subjectId] || subjectIconMap['default'];
|
| }
|
|
|
|
|
|
|
|
|
| const socket = io();
|
|
|
| socket.on('connect', function() {
|
| socket.emit('register_session', {
|
| username: USERNAME,
|
| session_id: SESSION_ID
|
| });
|
|
|
| setInterval(function() {
|
| socket.emit('check_session', {
|
| username: USERNAME,
|
| session_id: SESSION_ID
|
| });
|
| }, 30000);
|
| });
|
|
|
| socket.on('force_logout', function(data) {
|
| alert(data.message || 'تم تسجيل خروجك من جهاز آخر.');
|
| window.location.href = '/login';
|
| });
|
|
|
| socket.on('session_valid', function(data) {
|
| console.log('✅ Session valid');
|
| });
|
|
|
| socket.on('disconnect', function() {
|
| console.log('🔌 Socket disconnected');
|
| });
|
|
|
|
|
|
|
|
|
| async function safeFetch(url, options = {}) {
|
| const response = await fetch(url, options);
|
| if (response.status === 401) {
|
| window.location.href = '/login';
|
| return null;
|
| }
|
| return response;
|
| }
|
|
|
|
|
|
|
|
|
| function showToast(message, type = 'info', duration = 3500) {
|
| const toast = document.getElementById('toast');
|
|
|
| toast.innerHTML = message;
|
| toast.className = `toast toast-${type} show`;
|
| setTimeout(() => { toast.className = 'toast'; }, duration);
|
| }
|
|
|
|
|
|
|
|
|
| class MarkdownParser {
|
| static parse(text) {
|
| if (!text) return '';
|
| const protectedBlocks = [];
|
| let blockIndex = 0;
|
|
|
| function protect(content) {
|
| const placeholder = `\x00BLOCK${blockIndex}\x00`;
|
| protectedBlocks.push(content);
|
| blockIndex++;
|
| return placeholder;
|
| }
|
|
|
| text = text.replace(/\$\$([\s\S]*?)\$\$/g, (match) => protect(match));
|
| text = text.replace(/\\\[([\s\S]*?)\\\]/g, (match) => protect(match));
|
| text = text.replace(/```(\w*)\n?([\s\S]*?)```/g, (match, lang, code) => {
|
| const escapedCode = MarkdownParser._escapeHtml(code.trim());
|
| const codeId = `code_${Date.now()}_${blockIndex}`;
|
| const html = `<pre><div class="code-header"><span>${lang || 'text'}</span><button class="copy-btn" onclick="copyCode('${codeId}')"><i class="fas fa-copy"></i> نسخ</button></div><code id="${codeId}" class="language-${lang || 'text'}">${escapedCode}</code></pre>`;
|
| return protect(html);
|
| });
|
| text = text.replace(/`([^`\n]+)`/g, (match, code) => protect(`<code>${MarkdownParser._escapeHtml(code)}</code>`));
|
| text = text.replace(/\\\([\s\S]*?\\\)/g, (match) => protect(match));
|
| text = text.replace(/(?<![\\$])\$(?!\s)([^\$\n]+?)(?<!\s)\$(?!\$)/g, (match) => protect(match));
|
|
|
| let lines = text.split('\n');
|
| let html = '';
|
| let inList = false;
|
| let listType = '';
|
| let inBlockquote = false;
|
| let inTable = false;
|
| let tableRows = [];
|
| let i = 0;
|
|
|
| while (i < lines.length) {
|
| let line = lines[i];
|
|
|
| if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
|
| if (!inTable) {
|
| if (inList) { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
|
| inTable = true; tableRows = [];
|
| }
|
| tableRows.push(line.trim()); i++; continue;
|
| } else if (inTable) {
|
| html += MarkdownParser._renderTable(tableRows);
|
| inTable = false; tableRows = []; continue;
|
| }
|
|
|
| if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
|
| if (inList) { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
|
| html += '<hr>'; i++; continue;
|
| }
|
|
|
| const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
|
| if (headingMatch) {
|
| if (inList) { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
|
| const level = headingMatch[1].length;
|
| html += `<h${level}>${MarkdownParser._parseInline(headingMatch[2])}</h${level}>`;
|
| i++; continue;
|
| }
|
|
|
| if (line.trim().startsWith('>')) {
|
| if (inList) { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (!inBlockquote) { html += '<blockquote>'; inBlockquote = true; }
|
| html += `<p>${MarkdownParser._parseInline(line.trim().replace(/^>\s*/, ''))}</p>`;
|
| i++; continue;
|
| } else if (inBlockquote) {
|
| html += '</blockquote>'; inBlockquote = false;
|
| }
|
|
|
| const ulMatch = line.match(/^(\s*)([-*+])\s+(.+)/);
|
| if (ulMatch) {
|
| if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
|
| if (inList && listType !== 'ul') { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (!inList) { html += '<ul>'; inList = true; listType = 'ul'; }
|
| html += `<li>${MarkdownParser._parseInline(ulMatch[3])}</li>`;
|
| i++; continue;
|
| }
|
|
|
| const olMatch = line.match(/^(\s*)\d+[.)]\s+(.+)/);
|
| if (olMatch) {
|
| if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
|
| if (inList && listType !== 'ol') { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (!inList) { html += '<ol>'; inList = true; listType = 'ol'; }
|
| html += `<li>${MarkdownParser._parseInline(olMatch[2])}</li>`;
|
| i++; continue;
|
| }
|
|
|
| if (inList && line.trim() !== '') { html += MarkdownParser._closeList(listType); inList = false; }
|
| if (line.trim() === '') { i++; continue; }
|
| html += `<p>${MarkdownParser._parseInline(line)}</p>`;
|
| i++;
|
| }
|
|
|
| if (inList) html += MarkdownParser._closeList(listType);
|
| if (inBlockquote) html += '</blockquote>';
|
| if (inTable) html += MarkdownParser._renderTable(tableRows);
|
|
|
| html = html.replace(/\x00BLOCK(\d+)\x00/g, (match, idx) => protectedBlocks[parseInt(idx)] || match);
|
| return html;
|
| }
|
|
|
| static _parseInline(text) {
|
| if (!text) return '';
|
| text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
| text = text.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
| text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
|
| text = text.replace(/(?<![_\w])_(?!_)(.+?)(?<!_)_(?![_\w])/g, '<em>$1</em>');
|
| text = text.replace(/~~(.+?)~~/g, '<del>$1</del>');
|
| text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
| return text;
|
| }
|
|
|
| static _renderTable(rows) {
|
| if (rows.length < 1) return '';
|
| const parseRow = (row) => row.split('|').filter((_, i, arr) => i > 0 && i < arr.length - 1).map(cell => cell.trim());
|
| const headerCells = parseRow(rows[0]);
|
| let dataStartIndex = (rows.length > 1 && /^[\s|:-]+$/.test(rows[1])) ? 2 : 1;
|
| let tableHtml = '<div class="table-wrapper"><table><thead><tr>';
|
| headerCells.forEach(cell => { tableHtml += `<th>${MarkdownParser._parseInline(cell)}</th>`; });
|
| tableHtml += '</tr></thead><tbody>';
|
| for (let i = dataStartIndex; i < rows.length; i++) {
|
| const cells = parseRow(rows[i]);
|
| if (cells.length === 0) continue;
|
| tableHtml += '<tr>';
|
| cells.forEach(cell => { tableHtml += `<td>${MarkdownParser._parseInline(cell)}</td>`; });
|
| tableHtml += '</tr>';
|
| }
|
| tableHtml += '</tbody></table></div>';
|
| return tableHtml;
|
| }
|
|
|
| static _closeList(type) { return type === 'ol' ? '</ol>' : '</ul>'; }
|
|
|
| static _escapeHtml(text) {
|
| const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
| return text.replace(/[&<>"']/g, m => map[m]);
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| function copyCode(codeId) {
|
| const el = document.getElementById(codeId);
|
| if (el) {
|
| navigator.clipboard.writeText(el.textContent).then(() => {
|
| const btn = el.parentElement.querySelector('.copy-btn');
|
| if (btn) { btn.innerHTML = '<i class="fas fa-check"></i> تم ✓'; setTimeout(() => { btn.innerHTML = '<i class="fas fa-copy"></i> نسخ'; }, 2000); }
|
| }).catch(() => {
|
| const ta = document.createElement('textarea');
|
| ta.value = el.textContent;
|
| document.body.appendChild(ta);
|
| ta.select();
|
| document.execCommand('copy');
|
| document.body.removeChild(ta);
|
| });
|
| }
|
| }
|
|
|
| function renderMath(element) {
|
| if (window.MathJax && MathJax.typesetPromise) {
|
| MathJax.typesetPromise([element]).then(() => fixArabicInMath()).catch(err => console.warn('MathJax:', err));
|
| }
|
| }
|
|
|
| function highlightCode(element) {
|
| element.querySelectorAll('pre code').forEach(block => { try { hljs.highlightElement(block); } catch(e) {} });
|
| }
|
|
|
| function autoResize(textarea) {
|
| textarea.style.height = 'auto';
|
| textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
|
| }
|
|
|
| function handleKeyDown(event) {
|
| if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }
|
| }
|
|
|
| function sendQuick(text) {
|
| document.getElementById('messageInput').value = text;
|
| sendMessage();
|
| }
|
|
|
| function scrollToBottom() {
|
| const container = document.getElementById('chatContainer');
|
| setTimeout(() => { container.scrollTop = container.scrollHeight; }, 50);
|
| }
|
|
|
|
|
|
|
|
|
| function addUserMessage(content, imageUrl = null) {
|
| const container = document.getElementById('chatContainer');
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'none';
|
|
|
| const messageDiv = document.createElement('div');
|
| messageDiv.className = 'message user';
|
|
|
| const avatar = document.createElement('div');
|
| avatar.className = 'message-avatar';
|
| avatar.innerHTML = '<i class="fas fa-user"></i>';
|
|
|
| const wrapper = document.createElement('div');
|
| wrapper.className = 'message-wrapper';
|
|
|
| const contentDiv = document.createElement('div');
|
| contentDiv.className = 'message-content';
|
|
|
| if (imageUrl) {
|
| const imgWrapper = document.createElement('div');
|
| imgWrapper.className = 'message-image-wrapper';
|
| const img = document.createElement('img');
|
| img.src = imageUrl;
|
| img.className = 'message-image';
|
| img.alt = 'صورة مرسلة';
|
| img.onclick = () => window.open(imageUrl, '_blank');
|
| imgWrapper.appendChild(img);
|
| contentDiv.appendChild(imgWrapper);
|
| }
|
|
|
| if (content) {
|
| const textSpan = document.createElement('span');
|
| textSpan.textContent = content;
|
| contentDiv.appendChild(textSpan);
|
| }
|
|
|
| const metaDiv = document.createElement('div');
|
| metaDiv.className = 'message-meta';
|
| const timeSpan = document.createElement('span');
|
| timeSpan.textContent = new Date().toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| metaDiv.appendChild(timeSpan);
|
|
|
| wrapper.appendChild(contentDiv);
|
| wrapper.appendChild(metaDiv);
|
| messageDiv.appendChild(avatar);
|
| messageDiv.appendChild(wrapper);
|
|
|
| const typingIndicator = document.getElementById('typingIndicator');
|
| container.insertBefore(messageDiv, typingIndicator);
|
|
|
| messageCounter++;
|
| document.getElementById('messageCount').textContent = `${messageCounter} رسائل`;
|
| scrollToBottom();
|
| }
|
|
|
|
|
|
|
|
|
| function addUserMessageFromHistory(content, timestamp = null) {
|
| const container = document.getElementById('chatContainer');
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'none';
|
|
|
| const messageDiv = document.createElement('div');
|
| messageDiv.className = 'message user';
|
|
|
| const avatar = document.createElement('div');
|
| avatar.className = 'message-avatar';
|
| avatar.innerHTML = '<i class="fas fa-user"></i>';
|
|
|
| const wrapper = document.createElement('div');
|
| wrapper.className = 'message-wrapper';
|
|
|
| const contentDiv = document.createElement('div');
|
| contentDiv.className = 'message-content';
|
|
|
| if (content.startsWith('[صورة دراسية]')) {
|
| const lines = content.split('\n');
|
| const questionLine = lines.find(l => l.startsWith('سؤال الطالب:'));
|
| const displayText = questionLine
|
| ? questionLine.replace('سؤال الطالب:', '').trim()
|
| : 'أرسل صورة دراسية';
|
|
|
| const imgNotice = document.createElement('div');
|
| imgNotice.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 10px;background:rgba(124,58,237,0.08);border-radius:8px;margin-bottom:6px;font-size:13px;color:#7c3aed;';
|
| imgNotice.innerHTML = '<i class="fas fa-image"></i> صورة دراسية';
|
| contentDiv.appendChild(imgNotice);
|
|
|
| if (displayText && displayText !== 'أرسل صورة دراسية') {
|
| const textSpan = document.createElement('span');
|
| textSpan.textContent = displayText;
|
| contentDiv.appendChild(textSpan);
|
| }
|
| } else {
|
| const textSpan = document.createElement('span');
|
| textSpan.textContent = content;
|
| contentDiv.appendChild(textSpan);
|
| }
|
|
|
| const metaDiv = document.createElement('div');
|
| metaDiv.className = 'message-meta';
|
| const timeSpan = document.createElement('span');
|
|
|
| if (timestamp) {
|
| try {
|
| const date = new Date(timestamp);
|
| timeSpan.textContent = date.toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| } catch(e) {
|
| timeSpan.textContent = new Date().toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| }
|
| } else {
|
| timeSpan.textContent = new Date().toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| }
|
|
|
| metaDiv.appendChild(timeSpan);
|
| wrapper.appendChild(contentDiv);
|
| wrapper.appendChild(metaDiv);
|
| messageDiv.appendChild(avatar);
|
| messageDiv.appendChild(wrapper);
|
|
|
| const typingIndicator = document.getElementById('typingIndicator');
|
| container.insertBefore(messageDiv, typingIndicator);
|
|
|
| messageCounter++;
|
| document.getElementById('messageCount').textContent = `${messageCounter} رسائل`;
|
| }
|
|
|
|
|
|
|
|
|
| function addBotMessageFromHistory(content, timestamp = null) {
|
| const container = document.getElementById('chatContainer');
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'none';
|
|
|
| const messageDiv = document.createElement('div');
|
| messageDiv.className = 'message bot';
|
|
|
| const avatar = document.createElement('div');
|
| avatar.className = 'message-avatar';
|
| avatar.style.cssText = 'background:rgba(255,255,255,0.80);border:1.5px solid rgba(124,58,237,0.18);';
|
| avatar.innerHTML = '<i class="fas fa-user-graduate"></i>';
|
|
|
| const wrapper = document.createElement('div');
|
| wrapper.className = 'message-wrapper';
|
|
|
| const contentDiv = document.createElement('div');
|
| contentDiv.className = 'message-content';
|
|
|
| try {
|
| const html = MarkdownParser.parse(content);
|
| contentDiv.innerHTML = html;
|
| highlightCode(contentDiv);
|
| setTimeout(() => { renderMath(contentDiv); }, 50);
|
| } catch(e) {
|
| contentDiv.innerHTML = content
|
| .replace(/&/g, '&')
|
| .replace(/</g, '<')
|
| .replace(/>/g, '>')
|
| .replace(/\n/g, '<br>');
|
| }
|
|
|
| const metaDiv = document.createElement('div');
|
| metaDiv.className = 'message-meta';
|
| const timeSpan = document.createElement('span');
|
|
|
| if (timestamp) {
|
| try {
|
| const date = new Date(timestamp);
|
| timeSpan.textContent = date.toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| } catch(e) {
|
| timeSpan.textContent = new Date().toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| }
|
| } else {
|
| timeSpan.textContent = new Date().toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| }
|
|
|
| const historyBadge = document.createElement('span');
|
| historyBadge.className = 'file-badge';
|
| historyBadge.textContent = '📂 محفوظ';
|
| historyBadge.style.cssText = 'opacity:0.6;font-size:10px;';
|
|
|
| metaDiv.appendChild(timeSpan);
|
| metaDiv.appendChild(historyBadge);
|
| wrapper.appendChild(contentDiv);
|
| wrapper.appendChild(metaDiv);
|
| messageDiv.appendChild(avatar);
|
| messageDiv.appendChild(wrapper);
|
|
|
| const typingIndicator = document.getElementById('typingIndicator');
|
| container.insertBefore(messageDiv, typingIndicator);
|
|
|
| messageCounter++;
|
| document.getElementById('messageCount').textContent = `${messageCounter} رسائل`;
|
| }
|
|
|
|
|
|
|
|
|
| function addHistorySeparator(text) {
|
| const container = document.getElementById('chatContainer');
|
| const sep = document.createElement('div');
|
| sep.className = 'history-separator';
|
| sep.innerHTML = `
|
| <div class="history-separator-line"></div>
|
| <span class="history-separator-text">${text}</span>
|
| <div class="history-separator-line"></div>
|
| `;
|
| const typingIndicator = document.getElementById('typingIndicator');
|
| container.insertBefore(sep, typingIndicator);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function renderHistoryFromMessages(messages, subjectId, subjectName) {
|
| if (!messages || messages.length === 0) {
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'block';
|
| return false;
|
| }
|
|
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'none';
|
|
|
| const savedDate = messages[0]?.timestamp
|
| ? new Date(messages[0].timestamp).toLocaleDateString('ar-EG', {
|
| year: 'numeric', month: 'long', day: 'numeric'
|
| })
|
| : 'محادثة سابقة';
|
|
|
| addHistorySeparator(`<i class="fas fa-folder-open"></i> سجل محادثة ${subjectName} - ${savedDate}`);
|
|
|
| for (const msg of messages) {
|
| if (msg.role === 'user') {
|
| addUserMessageFromHistory(msg.content, msg.timestamp);
|
| } else if (msg.role === 'assistant') {
|
| addBotMessageFromHistory(msg.content, msg.timestamp);
|
| }
|
| }
|
|
|
| addHistorySeparator('<i class="fas fa-pen"></i> المحادثة الجديدة');
|
|
|
| document.getElementById('messageCount').textContent = `${messageCounter} رسائل`;
|
| setTimeout(() => { scrollToBottom(); }, 500);
|
|
|
| console.log(`✅ Rendered ${messages.length} messages for subject: ${subjectId}`);
|
| return true;
|
| }
|
|
|
|
|
|
|
|
|
| function createBotMessage(file = null) {
|
| const container = document.getElementById('chatContainer');
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'none';
|
|
|
| const messageDiv = document.createElement('div');
|
| messageDiv.className = 'message bot';
|
|
|
| const avatar = document.createElement('div');
|
| avatar.className = 'message-avatar';
|
| avatar.style.cssText = 'background:rgba(255,255,255,0.80);border:1.5px solid rgba(124,58,237,0.18);';
|
| avatar.innerHTML = '<i class="fas fa-user-graduate"></i>';
|
|
|
| const wrapper = document.createElement('div');
|
| wrapper.className = 'message-wrapper';
|
|
|
| const contentDiv = document.createElement('div');
|
| contentDiv.className = 'message-content';
|
|
|
| const metaDiv = document.createElement('div');
|
| metaDiv.className = 'message-meta';
|
| const timeSpan = document.createElement('span');
|
| timeSpan.textContent = new Date().toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });
|
| metaDiv.appendChild(timeSpan);
|
|
|
| if (file) {
|
| const fileBadge = document.createElement('span');
|
| fileBadge.className = 'file-badge';
|
| fileBadge.textContent = file;
|
| metaDiv.appendChild(fileBadge);
|
| }
|
|
|
| wrapper.appendChild(contentDiv);
|
| wrapper.appendChild(metaDiv);
|
| messageDiv.appendChild(avatar);
|
| messageDiv.appendChild(wrapper);
|
|
|
| const typingIndicator = document.getElementById('typingIndicator');
|
| container.insertBefore(messageDiv, typingIndicator);
|
|
|
| messageCounter++;
|
| document.getElementById('messageCount').textContent = `${messageCounter} رسائل`;
|
| scrollToBottom();
|
| return contentDiv;
|
| }
|
|
|
| function renderBotContent(contentDiv, rawText) {
|
| const html = MarkdownParser.parse(rawText);
|
| contentDiv.innerHTML = html;
|
| highlightCode(contentDiv);
|
| renderMath(contentDiv);
|
| scrollToBottom();
|
| }
|
|
|
| function showTyping() {
|
| document.getElementById('typingIndicator').classList.add('active');
|
| scrollToBottom();
|
| }
|
|
|
| function hideTyping() {
|
| document.getElementById('typingIndicator').classList.remove('active');
|
| }
|
|
|
| function updateStatus(sessionInfo) {
|
| if (sessionInfo) {
|
| document.getElementById('currentFile').textContent = sessionInfo.last_file || '-';
|
| document.getElementById('summaryStatus').textContent = sessionInfo.has_summary ? 'ملخص متوفر ✓' : 'لا يوجد ملخص';
|
| document.getElementById('totalMessages').textContent = sessionInfo.total_messages || 0;
|
| if (sessionInfo.subject) document.getElementById('currentSubjectStatus').textContent = sessionInfo.subject;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| async function sendMessage() {
|
| const input = document.getElementById('messageInput');
|
| const message = input.value.trim();
|
|
|
| if (pendingImageUrl && pendingImageExtracted && !imageAnalyzing) {
|
| await sendImageMessage(message);
|
| return;
|
| }
|
|
|
| if (!message || isProcessing) return;
|
|
|
| isProcessing = true;
|
| document.getElementById('sendBtn').disabled = true;
|
|
|
| addUserMessage(message);
|
| input.value = '';
|
| input.style.height = 'auto';
|
| showTyping();
|
|
|
| try {
|
| const response = await safeFetch('/chat', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ message: message })
|
| });
|
|
|
| if (!response) return;
|
|
|
| const data = await response.json();
|
|
|
| if (data.error) {
|
| hideTyping();
|
| if (data.need_subject) { goToSubjectSelection(); return; }
|
| if (data.code === 'no_access') {
|
| const errorDiv = createBotMessage();
|
| errorDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> ${data.error}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| return;
|
| }
|
| const errorDiv = createBotMessage();
|
| errorDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> ${data.error}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| return;
|
| }
|
|
|
| if (data.session_info) updateStatus(data.session_info);
|
| document.getElementById('currentFile').textContent = data.chosen_file || '-';
|
|
|
| if (data.type === 'gpt') {
|
| hideTyping();
|
| typewriterEffect(data.response, data.chosen_file);
|
| } else if (data.type === 'mistral') {
|
| hideTyping();
|
| await streamResponse(data.stream_url, data.chosen_file);
|
| }
|
|
|
| } catch (error) {
|
| hideTyping();
|
| const errorDiv = createBotMessage();
|
| errorDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> خطأ في الاتصال: ${error.message}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| input.focus();
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| async function sendImageMessage(extraText = '') {
|
| if (!pendingImageUrl || !pendingImageExtracted) return;
|
| if (isProcessing) return;
|
|
|
| isProcessing = true;
|
| document.getElementById('sendBtn').disabled = true;
|
|
|
| const userText = extraText
|
| ? `[صورة دراسية]\n\n${pendingImageExtracted}\n\nسؤال الطالب: ${extraText}`
|
| : `[صورة دراسية]\n\n${pendingImageExtracted}`;
|
|
|
| addUserMessage(extraText || 'أرسل صورة دراسية', pendingImageUrl);
|
|
|
| pendingImageUrl = null;
|
| pendingImageExtracted = null;
|
| pendingImageFile = null;
|
| hideImagePreview();
|
|
|
| const input = document.getElementById('messageInput');
|
| input.value = '';
|
| input.style.height = 'auto';
|
|
|
| showTyping();
|
|
|
| try {
|
| const response = await safeFetch('/chat', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ message: userText })
|
| });
|
|
|
| if (!response) return;
|
|
|
| const data = await response.json();
|
|
|
| if (data.error) {
|
| hideTyping();
|
| if (data.need_subject) { goToSubjectSelection(); return; }
|
| const errorDiv = createBotMessage();
|
| errorDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> ${data.error}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| return;
|
| }
|
|
|
| if (data.session_info) updateStatus(data.session_info);
|
| document.getElementById('currentFile').textContent = data.chosen_file || '-';
|
|
|
| if (data.type === 'gpt') {
|
| hideTyping();
|
| typewriterEffect(data.response, data.chosen_file);
|
| } else if (data.type === 'mistral') {
|
| hideTyping();
|
| await streamResponse(data.stream_url, data.chosen_file);
|
| }
|
|
|
| } catch (error) {
|
| hideTyping();
|
| const errorDiv = createBotMessage();
|
| errorDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> خطأ في الاتصال: ${error.message}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| function typewriterEffect(text, file) {
|
| const contentDiv = createBotMessage(file);
|
| let index = 0;
|
| let renderTimer = null;
|
| let isRendering = false;
|
|
|
| contentDiv.innerHTML = '<span class="streaming-cursor"></span>';
|
|
|
| function scheduleFullRender() {
|
| if (renderTimer) clearTimeout(renderTimer);
|
| renderTimer = setTimeout(() => {
|
| if (!isRendering) {
|
| isRendering = true;
|
| try {
|
| const html = MarkdownParser.parse(text.substr(0, index));
|
| contentDiv.innerHTML = html + '<span class="streaming-cursor"></span>';
|
| highlightCode(contentDiv);
|
| renderMath(contentDiv);
|
| } catch(e) {}
|
| finally { isRendering = false; }
|
| scrollToBottom();
|
| }
|
| }, 100);
|
| }
|
|
|
| function type() {
|
| if (index < text.length) {
|
| index += Math.min(6, text.length - index);
|
| scheduleFullRender();
|
| setTimeout(type, 10);
|
| } else {
|
| if (renderTimer) clearTimeout(renderTimer);
|
| try { renderBotContent(contentDiv, text); } catch(e) { contentDiv.textContent = text; }
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| document.getElementById('messageInput').focus();
|
| refreshSessionInfo();
|
| }
|
| }
|
| type();
|
| }
|
|
|
|
|
|
|
|
|
| async function streamResponse(streamUrl, file) {
|
| const contentDiv = createBotMessage(file);
|
| let fullText = '';
|
| let renderTimer = null;
|
| let isRendering = false;
|
| let streamDone = false;
|
|
|
| contentDiv.innerHTML = '<span class="streaming-cursor"></span>';
|
|
|
| function scheduleRender() {
|
| if (renderTimer) clearTimeout(renderTimer);
|
| renderTimer = setTimeout(() => {
|
| if (!isRendering && fullText) {
|
| isRendering = true;
|
| const html = MarkdownParser.parse(fullText);
|
| contentDiv.innerHTML = html + (streamDone ? '' : '<span class="streaming-cursor"></span>');
|
| highlightCode(contentDiv);
|
| renderMath(contentDiv);
|
| isRendering = false;
|
| scrollToBottom();
|
| }
|
| }, 120);
|
| }
|
|
|
| return new Promise((resolve) => {
|
| try {
|
| const eventSource = new EventSource(streamUrl);
|
|
|
| eventSource.onmessage = function(event) {
|
| try {
|
| const data = JSON.parse(event.data);
|
| if (data.error) {
|
| eventSource.close();
|
| streamDone = true;
|
| if (renderTimer) clearTimeout(renderTimer);
|
| contentDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> ${data.error}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| resolve(); return;
|
| }
|
| if (data.chunk) { fullText += data.chunk; scheduleRender(); }
|
| if (data.done) {
|
| eventSource.close();
|
| streamDone = true;
|
| if (renderTimer) clearTimeout(renderTimer);
|
| renderBotContent(contentDiv, fullText);
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| refreshSessionInfo();
|
| resolve();
|
| }
|
| } catch(e) { console.error('Stream parse error:', e); }
|
| };
|
|
|
| eventSource.onerror = function() {
|
| eventSource.close();
|
| streamDone = true;
|
| if (renderTimer) clearTimeout(renderTimer);
|
| if (!fullText) {
|
| contentDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> خطأ في الاتصال. حاول مرة أخرى.</p>`;
|
| } else {
|
| renderBotContent(contentDiv, fullText);
|
| }
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| resolve();
|
| };
|
| } catch(error) {
|
| contentDiv.innerHTML = `<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> ${error.message}</p>`;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| resolve();
|
| }
|
| });
|
| }
|
|
|
|
|
|
|
|
|
| async function refreshSessionInfo() {
|
| try {
|
| const response = await safeFetch('/session-info', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({})
|
| });
|
| if (!response) return;
|
| const info = await response.json();
|
| updateStatus(info);
|
| } catch(e) {}
|
| }
|
|
|
|
|
|
|
|
|
| async function toggleVoiceRecording() {
|
| if (isRecording) { stopVoiceRecording(); } else { await startVoiceRecording(); }
|
| }
|
|
|
| async function startVoiceRecording() {
|
| try {
|
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| audioChunks = [];
|
| mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
|
|
|
| mediaRecorder.ondataavailable = (e) => {
|
| if (e.data.size > 0) audioChunks.push(e.data);
|
| };
|
|
|
| mediaRecorder.onstop = async () => {
|
| stream.getTracks().forEach(t => t.stop());
|
| await processVoiceRecording();
|
| };
|
|
|
| mediaRecorder.start(100);
|
| isRecording = true;
|
|
|
| document.getElementById('voiceRecordingUI').style.display = 'block';
|
| document.getElementById('voiceBtn').classList.add('recording');
|
| document.getElementById('imageBtn').disabled = true;
|
| document.getElementById('voiceBtn').innerHTML = '<i class="fas fa-microphone-slash"></i>';
|
|
|
| voiceSeconds = 0;
|
| updateVoiceTimer();
|
| voiceTimerInterval = setInterval(() => {
|
| voiceSeconds++;
|
| updateVoiceTimer();
|
| if (voiceSeconds >= 120) stopVoiceRecording();
|
| }, 1000);
|
|
|
| } catch(error) {
|
| showToast('<i class="fas fa-microphone-slash"></i> لا يمكن الوصول إلى الميكروفون. تحقق من الأذونات.', 'error');
|
| }
|
| }
|
|
|
| function updateVoiceTimer() {
|
| const mins = String(Math.floor(voiceSeconds / 60)).padStart(2, '0');
|
| const secs = String(voiceSeconds % 60).padStart(2, '0');
|
| document.getElementById('voiceTimer').textContent = `${mins}:${secs}`;
|
| }
|
|
|
| function stopVoiceRecording() {
|
| if (mediaRecorder && isRecording) {
|
| mediaRecorder.stop();
|
| isRecording = false;
|
| clearInterval(voiceTimerInterval);
|
| document.getElementById('voiceBtn').classList.remove('recording');
|
| document.getElementById('voiceRecordingUI').style.display = 'none';
|
| document.getElementById('imageBtn').disabled = false;
|
| document.getElementById('voiceBtn').innerHTML = '<i class="fas fa-microphone"></i>';
|
| }
|
| }
|
|
|
| function cancelVoiceRecording() {
|
| if (mediaRecorder && isRecording) {
|
| mediaRecorder.onstop = () => {};
|
| mediaRecorder.stop();
|
| if (mediaRecorder.stream) {
|
| mediaRecorder.stream.getTracks().forEach(t => t.stop());
|
| }
|
| isRecording = false;
|
| audioChunks = [];
|
| clearInterval(voiceTimerInterval);
|
| document.getElementById('voiceBtn').classList.remove('recording');
|
| document.getElementById('voiceRecordingUI').style.display = 'none';
|
| document.getElementById('imageBtn').disabled = false;
|
| document.getElementById('voiceBtn').innerHTML = '<i class="fas fa-microphone"></i>';
|
| showToast('<i class="fas fa-times-circle"></i> تم إلغاء التسجيل', 'info');
|
| }
|
| }
|
|
|
| async function processVoiceRecording() {
|
| if (audioChunks.length === 0) {
|
| showToast('<i class="fas fa-exclamation-circle"></i> لم يتم تسجيل أي صوت', 'error');
|
| return;
|
| }
|
|
|
| const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
| audioChunks = [];
|
|
|
| const voiceBtn = document.getElementById('voiceBtn');
|
| voiceBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
| voiceBtn.disabled = true;
|
| document.getElementById('sendBtn').disabled = true;
|
| showToast('<i class="fas fa-microphone"></i> جاري معالجة التسجيل...', 'info', 10000);
|
|
|
| try {
|
| const formData = new FormData();
|
| formData.append('audio', audioBlob, 'recording.webm');
|
|
|
| const uploadResponse = await safeFetch('/voice-upload', {
|
| method: 'POST',
|
| body: formData
|
| });
|
|
|
| if (!uploadResponse) return;
|
|
|
| const uploadData = await uploadResponse.json();
|
|
|
| if (uploadData.error) {
|
| showToast(`<i class="fas fa-exclamation-circle"></i> ${uploadData.error}`, 'error');
|
| resetVoiceBtn();
|
| return;
|
| }
|
|
|
| const transcribeResponse = await safeFetch('/voice-transcribe', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ file_url: uploadData.file_url })
|
| });
|
|
|
| if (!transcribeResponse) return;
|
|
|
| const transcribeData = await transcribeResponse.json();
|
|
|
| if (transcribeData.error) {
|
| showToast(`<i class="fas fa-exclamation-circle"></i> ${transcribeData.error}`, 'error');
|
| resetVoiceBtn();
|
| return;
|
| }
|
|
|
| const transcription = transcribeData.transcription.trim();
|
|
|
| if (!transcription) {
|
| showToast('<i class="fas fa-exclamation-circle"></i> لم يتم التعرف على أي كلام', 'error');
|
| resetVoiceBtn();
|
| return;
|
| }
|
|
|
| showToast('<i class="fas fa-check-circle"></i> تم التعرف على الصوت بنجاح', 'success');
|
| document.getElementById('messageInput').value = transcription;
|
| autoResize(document.getElementById('messageInput'));
|
| resetVoiceBtn();
|
| await sendMessage();
|
|
|
| } catch(error) {
|
| showToast(`<i class="fas fa-exclamation-circle"></i> خطأ في معالجة الصوت: ${error.message}`, 'error');
|
| resetVoiceBtn();
|
| }
|
| }
|
|
|
| function resetVoiceBtn() {
|
| const voiceBtn = document.getElementById('voiceBtn');
|
| voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>';
|
| voiceBtn.disabled = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| }
|
|
|
|
|
|
|
|
|
| async function handleImageSelected(event) {
|
| const file = event.target.files[0];
|
| if (!file) return;
|
|
|
| event.target.value = '';
|
|
|
| if (!file.type.startsWith('image/')) {
|
| showToast('<i class="fas fa-exclamation-triangle"></i> يرجى اختيار ملف صورة صالح', 'error');
|
| return;
|
| }
|
|
|
| if (file.size > 10 * 1024 * 1024) {
|
| showToast('<i class="fas fa-exclamation-triangle"></i> حجم الصورة كبير جداً. الحد الأقصى 10 ميجابايت', 'error');
|
| return;
|
| }
|
|
|
| pendingImageFile = file;
|
|
|
| const reader = new FileReader();
|
| reader.onload = (e) => { showImagePreview(e.target.result, file.name); };
|
| reader.readAsDataURL(file);
|
|
|
| imageAnalyzing = true;
|
| document.getElementById('imageBtn').disabled = true;
|
| document.getElementById('voiceBtn').disabled = true;
|
| document.getElementById('sendBtn').disabled = true;
|
| setImagePreviewStatus('جاري تحليل الصورة...', 'analyzing');
|
|
|
| try {
|
| const formData = new FormData();
|
| formData.append('image', file, file.name);
|
|
|
| const response = await safeFetch('/image-upload-analyze', {
|
| method: 'POST',
|
| body: formData
|
| });
|
|
|
| if (!response) {
|
| hideImagePreview();
|
| resetImageState();
|
| return;
|
| }
|
|
|
| const data = await response.json();
|
|
|
| if (data.error) {
|
| if (data.need_subject) {
|
| hideImagePreview();
|
| goToSubjectSelection();
|
| return;
|
| }
|
| showToast(`<i class="fas fa-exclamation-circle"></i> ${data.error}`, 'error');
|
| hideImagePreview();
|
| resetImageState();
|
| return;
|
| }
|
|
|
| if (!data.supported) {
|
| hideImagePreview();
|
| resetImageState();
|
| showToast(data.message || '<i class="fas fa-exclamation-triangle"></i> هذه الصورة لا تحتوي على محتوى دراسي', 'error', 5000);
|
|
|
| const container = document.getElementById('chatContainer');
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'none';
|
|
|
| const notifDiv = document.createElement('div');
|
| notifDiv.className = 'unsupported-image-notif';
|
| notifDiv.innerHTML = `<span class="unsupported-icon"><i class="fas fa-ban"></i></span>
|
| <span>${data.message || 'هذه الصورة لا تحتوي على محتوى دراسي.'}</span>
|
| `;
|
| const typingIndicator = document.getElementById('typingIndicator');
|
| container.insertBefore(notifDiv, typingIndicator);
|
| scrollToBottom();
|
|
|
| setTimeout(() => {
|
| if (notifDiv.parentNode) notifDiv.parentNode.removeChild(notifDiv);
|
| }, 5000);
|
| return;
|
| }
|
|
|
| pendingImageUrl = data.image_url;
|
| pendingImageExtracted = data.extracted_text;
|
| imageAnalyzing = false;
|
|
|
| setImagePreviewStatus('<i class="fas fa-check"></i> تم التحليل - اضغط إرسال', 'ready');
|
| document.getElementById('imageBtn').disabled = false;
|
| document.getElementById('voiceBtn').disabled = false;
|
| document.getElementById('sendBtn').disabled = false;
|
|
|
| showToast('<i class="fas fa-check-circle"></i> تم تحليل الصورة بنجاح! اضغط إرسال أو أضف سؤالاً', 'success');
|
| document.getElementById('messageInput').focus();
|
| document.getElementById('messageInput').placeholder = 'أضف سؤالاً عن الصورة (اختياري) ثم اضغط إرسال...';
|
|
|
| } catch(error) {
|
| showToast(`<i class="fas fa-exclamation-circle"></i> خطأ في معالجة الصورة: ${error.message}`, 'error');
|
| hideImagePreview();
|
| resetImageState();
|
| }
|
| }
|
|
|
| function showImagePreview(src, filename) {
|
| const bar = document.getElementById('imagePreviewBar');
|
| const thumb = document.getElementById('imagePreviewThumb');
|
| const name = document.getElementById('imagePreviewName');
|
| thumb.src = src;
|
| name.textContent = filename || 'صورة';
|
| bar.style.display = 'block';
|
| }
|
|
|
| function hideImagePreview() {
|
| document.getElementById('imagePreviewBar').style.display = 'none';
|
| document.getElementById('imagePreviewThumb').src = '';
|
| }
|
|
|
| function setImagePreviewStatus(text, state) {
|
| const statusEl = document.getElementById('imagePreviewStatus');
|
|
|
| statusEl.innerHTML = text;
|
| statusEl.className = `image-preview-status status-${state}`;
|
| }
|
|
|
| function cancelImage() {
|
| if (imageAnalyzing) {
|
| showToast('<i class="fas fa-spinner fa-spin"></i> جاري التحليل، يرجى الانتظار...', 'info');
|
| return;
|
| }
|
| hideImagePreview();
|
| resetImageState();
|
| document.getElementById('messageInput').placeholder = 'اكتب سؤالك هنا';
|
| showToast('<i class="fas fa-times"></i> تم إلغاء الصورة', 'info');
|
| }
|
|
|
| function resetImageState() {
|
| pendingImageUrl = null;
|
| pendingImageExtracted = null;
|
| pendingImageFile = null;
|
| imageAnalyzing = false;
|
| document.getElementById('imageBtn').disabled = false;
|
| document.getElementById('voiceBtn').disabled = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| }
|
|
|
|
|
|
|
|
|
| async function loadUserAndSubjects() {
|
| const grid = document.getElementById('subjectsGrid');
|
| const startBtn = document.getElementById('startBtn');
|
|
|
| startBtn.disabled = true;
|
| startBtn.innerHTML = `<span><i class="fas fa-play"></i></span><span>ابدأ المحادثة</span>`;
|
| selectedSubjectId = null;
|
| selectedSubjectName = null;
|
|
|
| grid.innerHTML = `
|
| <div class="loading-subjects" style="grid-column:1/-1;">
|
| <div class="spinner"></div>
|
| <p>جاري تحميل المواد...</p>
|
| </div>`;
|
|
|
| try {
|
| const response = await safeFetch('/me');
|
| if (!response) return;
|
|
|
| const data = await response.json();
|
|
|
| if (!response.ok || data.error) {
|
| showAccessDenied(data.error || 'حدث خطأ غير متوقع');
|
| return;
|
| }
|
|
|
| currentUserData = data;
|
|
|
| document.getElementById('userDisplayName').textContent = data.username;
|
| document.getElementById('userTrackBadge').textContent = data.student_type || '';
|
| document.getElementById('headerUsername').textContent = data.username;
|
|
|
| const subjects = data.subjects || [];
|
|
|
| if (subjects.length === 0) {
|
| grid.innerHTML = `
|
| <div class="no-subjects" style="grid-column:1/-1;">
|
| <div class="no-subjects-icon"><i class="fas fa-inbox"></i></div>
|
| <p style="color:var(--clay-fg);font-weight:600;">لا توجد مواد متاحة حالياً</p>
|
| <button class="retry-btn" onclick="loadUserAndSubjects()"><i class="fas fa-sync-alt"></i> إعادة المحاولة</button>
|
| </div>`;
|
| return;
|
| }
|
|
|
| grid.innerHTML = '';
|
|
|
| subjects.forEach(subject => {
|
| const iconClass = getSubjectIcon(subject.id);
|
| const card = document.createElement('div');
|
|
|
| if (subject.accessible) {
|
| card.className = 'subject-card';
|
| card.dataset.subjectId = subject.id;
|
| card.dataset.subjectName = subject.name;
|
| card.innerHTML = `
|
| <span class="subject-icon"><i class="fas ${iconClass}"></i></span>
|
| <div class="subject-name">${subject.name}</div>
|
| <div class="subject-info">${subject.free ? 'مجاني' : 'مشترك'}</div>
|
| ${subject.free ? '<span class="subject-free-badge">مجاني</span>' : ''}
|
| `;
|
| card.onclick = () => selectSubjectCard(subject.id, subject.name, card);
|
| } else {
|
| card.className = 'subject-card locked';
|
| card.dataset.subjectId = subject.id;
|
| card.dataset.subjectName = subject.name;
|
| card.innerHTML = `
|
| <span class="subject-icon"><i class="fas ${iconClass}"></i></span>
|
| <div class="subject-name">${subject.name}</div>
|
| <div class="subject-info subject-locked-info"><i class="fas fa-lock"></i> يتطلب اشتراك</div>
|
| <span class="subject-price-badge">${subject.price} $</span>
|
| `;
|
| card.onclick = () => showToast(`<i class="fas fa-lock"></i> يجب شراء مادة "${subject.name}" للوصول إليها`, 'error', 4000);
|
| }
|
|
|
| grid.appendChild(card);
|
| });
|
|
|
|
|
|
|
| const urlParams = new URLSearchParams(window.location.search);
|
| const preSubject = urlParams.get('subject');
|
| if (preSubject) {
|
| const matchCard = grid.querySelector(`[data-subject-id="${preSubject}"]`);
|
| if (matchCard && !matchCard.classList.contains('locked')) {
|
| selectSubjectCard(preSubject, matchCard.dataset.subjectName, matchCard);
|
| }
|
| }
|
|
|
| } catch(error) {
|
| grid.innerHTML = `
|
| <div class="no-subjects" style="grid-column:1/-1;">
|
| <div class="no-subjects-icon"><i class="fas fa-exclamation-circle"></i></div>
|
| <p style="color:var(--clay-fg);font-weight:600;">خطأ في تحميل المواد</p>
|
| <p style="font-size:12px;margin-top:8px;color:#DB2777;">${error.message}</p>
|
| <button class="retry-btn" onclick="loadUserAndSubjects()"><i class="fas fa-sync-alt"></i> إعادة المحاولة</button>
|
| </div>`;
|
| }
|
| }
|
|
|
| function showAccessDenied(message) {
|
| document.getElementById('subjectPage').classList.add('hidden');
|
| document.getElementById('chatPage').classList.add('hidden');
|
| const accessPage = document.getElementById('accessDeniedPage');
|
| if (accessPage) {
|
| accessPage.classList.remove('hidden');
|
| document.getElementById('accessDeniedTitle').textContent = '<i class="fas fa-lock"></i> غير مصرح';
|
| document.getElementById('accessDeniedMsg').textContent = message;
|
| }
|
| }
|
|
|
| function selectSubjectCard(subjectId, subjectName, cardElement) {
|
| document.querySelectorAll('.subject-card').forEach(c => c.classList.remove('selected'));
|
| cardElement.classList.add('selected');
|
|
|
| selectedSubjectId = subjectId;
|
| selectedSubjectName = subjectName;
|
|
|
| const startBtn = document.getElementById('startBtn');
|
| startBtn.disabled = false;
|
| startBtn.innerHTML = `<span><i class="fas ${getSubjectIcon(subjectId)}"></i></span><span>ابدأ مع ${subjectName}</span>`;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| async function startChat() {
|
| if (!selectedSubjectId) {
|
| showToast('<i class="fas fa-exclamation-circle"></i> يرجى اختيار مادة أولاً', 'error');
|
| return;
|
| }
|
|
|
| const overlay = document.getElementById('transitionOverlay');
|
| document.getElementById('transitionText').textContent = `جاري تحميل مادة ${selectedSubjectName}...`;
|
| overlay.classList.add('active');
|
|
|
| try {
|
| const response = await safeFetch('/select-subject', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ subject: selectedSubjectId })
|
| });
|
|
|
| if (!response) {
|
| overlay.classList.remove('active');
|
| return;
|
| }
|
|
|
| const data = await response.json();
|
|
|
| if (!response.ok || data.error) {
|
| overlay.classList.remove('active');
|
| if (data.code === 'no_access') {
|
| showToast(`<i class="fas fa-lock"></i> ${data.error}`, 'error', 5000);
|
| } else {
|
| showToast(`<i class="fas fa-exclamation-circle"></i> ${data.error}`, 'error');
|
| }
|
| return;
|
| }
|
|
|
|
|
| await switchToChatPage(data);
|
|
|
| } catch(error) {
|
| overlay.classList.remove('active');
|
| showToast(`<i class="fas fa-exclamation-circle"></i> خطأ في الاتصال: ${error.message}`, 'error');
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| async function switchToChatPage(data) {
|
| const iconClass = getSubjectIcon(selectedSubjectId);
|
| const subjectName = data.subject_name || selectedSubjectName;
|
|
|
| document.getElementById('chatTitle').textContent = `مساعد ${subjectName} الذكي`;
|
| document.getElementById('chatSubtitle').textContent = `${data.chapters_count} فصول متاحة`;
|
| document.getElementById('currentSubjectName').textContent = subjectName;
|
| document.getElementById('currentSubjectStatus').textContent = subjectName;
|
| document.getElementById('welcomeIcon').innerHTML = `<i class="fas ${iconClass}"></i>`;
|
| document.getElementById('welcomeTitle').textContent = `مرحباً بك في مساعد ${subjectName}`;
|
| document.getElementById('welcomeText').textContent = `أنا مساعدك الذكي في مادة ${subjectName}. يمكنني مساعدتك في فهم جميع الفصول والمواضيع. اطرح سؤالك وسأجيبك بالتفصيل!`;
|
|
|
| document.getElementById('subjectPage').classList.add('hidden');
|
| document.getElementById('chatPage').classList.remove('hidden');
|
| document.getElementById('transitionOverlay').classList.remove('active');
|
|
|
|
|
| resetChatUI();
|
|
|
|
|
|
|
| const chatHistory = data.chat_history || [];
|
|
|
| if (chatHistory.length > 0) {
|
| historyLoaded = true;
|
| const hasHistory = renderHistoryFromMessages(
|
| chatHistory,
|
| selectedSubjectId,
|
| subjectName
|
| );
|
| if (hasHistory) {
|
| showToast(`<i class="fas fa-check-circle"></i> تم استعادة ${chatHistory.length} رسالة من سجل ${subjectName}`, 'success', 3000);
|
| document.getElementById('chatSubtitle').textContent = `${data.chapters_count} فصول - سجل محادثة محمّل`;
|
| }
|
| } else {
|
| historyLoaded = false;
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'block';
|
| }
|
|
|
| setTimeout(() => { document.getElementById('messageInput').focus(); }, 100);
|
| }
|
|
|
| function goToSubjectSelection() {
|
| if (isProcessing) {
|
| if (!confirm('هناك رسالة قيد المعالجة. هل تريد العودة لاختيار المادة؟')) return;
|
| isProcessing = false;
|
| document.getElementById('sendBtn').disabled = false;
|
| }
|
|
|
| if (imageAnalyzing) { resetImageState(); hideImagePreview(); }
|
| if (isRecording) { cancelVoiceRecording(); }
|
|
|
| document.getElementById('chatPage').classList.add('hidden');
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| historyLoaded = false;
|
| loadUserAndSubjects();
|
| }
|
|
|
| function resetChatUI() {
|
| const container = document.getElementById('chatContainer');
|
|
|
| container.querySelectorAll(
|
| '.message, .unsupported-image-notif, .history-separator'
|
| ).forEach(el => el.remove());
|
|
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'block';
|
|
|
| const historyLoading = document.getElementById('historyLoading');
|
| if (historyLoading) historyLoading.style.display = 'none';
|
|
|
| messageCounter = 0;
|
| document.getElementById('messageCount').textContent = '0 رسائل';
|
| document.getElementById('currentFile').textContent = '-';
|
| document.getElementById('summaryStatus').textContent = 'لا يوجد ملخص';
|
| document.getElementById('totalMessages').textContent = '0';
|
|
|
| hideImagePreview();
|
| resetImageState();
|
| document.getElementById('messageInput').placeholder = 'اكتب سؤالك هنا';
|
| }
|
|
|
|
|
|
|
|
|
| async function showSummary() {
|
| document.getElementById('summaryModal').classList.add('active');
|
| document.getElementById('summaryContent').textContent = 'جاري تحميل الملخص...';
|
|
|
| try {
|
| const response = await safeFetch('/summary', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({})
|
| });
|
| if (!response) return;
|
|
|
| const data = await response.json();
|
| const summaryDiv = document.getElementById('summaryContent');
|
| summaryDiv.innerHTML = MarkdownParser.parse(data.summary);
|
| renderMath(summaryDiv);
|
| highlightCode(summaryDiv);
|
|
|
| } catch(error) {
|
| document.getElementById('summaryContent').innerHTML =
|
| '<p style="color:#DB2777;font-weight:600;"><i class="fas fa-exclamation-circle"></i> خطأ في تحميل الملخص</p>';
|
| }
|
| }
|
|
|
| function closeSummaryModal() {
|
| document.getElementById('summaryModal').classList.remove('active');
|
| }
|
|
|
| document.getElementById('summaryModal').addEventListener('click', function(e) {
|
| if (e.target === this) closeSummaryModal();
|
| });
|
|
|
|
|
|
|
|
|
| async function clearChat() {
|
| if (!confirm('هل تريد مسح جميع الرسائل والملخصات للمادة الحالية؟')) return;
|
|
|
| try {
|
| const response = await safeFetch('/clear', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({})
|
| });
|
|
|
| if (!response) return;
|
|
|
|
|
| const container = document.getElementById('chatContainer');
|
| container.querySelectorAll(
|
| '.message, .unsupported-image-notif, .history-separator'
|
| ).forEach(el => el.remove());
|
|
|
| const welcome = document.getElementById('welcomeMessage');
|
| if (welcome) welcome.style.display = 'block';
|
|
|
| messageCounter = 0;
|
| historyLoaded = false;
|
| document.getElementById('messageCount').textContent = '0 رسائل';
|
| document.getElementById('currentFile').textContent = '-';
|
| document.getElementById('summaryStatus').textContent = 'لا يوجد ملخص';
|
| document.getElementById('totalMessages').textContent = '0';
|
|
|
| hideImagePreview();
|
| resetImageState();
|
|
|
|
|
|
|
| if (selectedSubjectId) {
|
| const reSelectResponse = await safeFetch('/select-subject', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ subject: selectedSubjectId })
|
| });
|
|
|
| }
|
|
|
| showToast('<i class="fas fa-check-circle"></i> تم مسح محادثة المادة الحالية بنجاح', 'success');
|
|
|
| } catch(error) {
|
| showToast('<i class="fas fa-exclamation-circle"></i> خطأ في مسح المحادثة', 'error');
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| document.addEventListener('keydown', function(e) {
|
| if (e.key === 'Escape') {
|
| closeSummaryModal();
|
| if (isRecording) cancelVoiceRecording();
|
| }
|
| });
|
|
|
|
|
|
|
|
|
| async function init() {
|
| if (!USERNAME) {
|
| window.location.href = '/login';
|
| return;
|
| }
|
|
|
| const urlParams = new URLSearchParams(window.location.search);
|
| const subjectFromUrl = urlParams.get('subject');
|
|
|
|
|
|
|
|
|
|
|
| if (subjectFromUrl) {
|
| console.log(`🔀 URL subject param detected: ${subjectFromUrl}`);
|
|
|
|
|
| const overlay = document.getElementById('transitionOverlay');
|
| document.getElementById('transitionText').textContent = 'جاري تحميل المادة...';
|
| overlay.classList.add('active');
|
|
|
| try {
|
|
|
| const meResponse = await safeFetch('/me');
|
| if (!meResponse) {
|
| overlay.classList.remove('active');
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| return;
|
| }
|
|
|
| const meData = await meResponse.json();
|
| if (meData.error) {
|
| overlay.classList.remove('active');
|
| showAccessDenied(meData.error);
|
| return;
|
| }
|
|
|
| currentUserData = meData;
|
| document.getElementById('userDisplayName').textContent = meData.username;
|
| document.getElementById('userTrackBadge').textContent = meData.student_type || '';
|
| document.getElementById('headerUsername').textContent = meData.username;
|
|
|
|
|
| const subjects = meData.subjects || [];
|
| const subjectInfo = subjects.find(s => s.id === subjectFromUrl);
|
|
|
| if (!subjectInfo) {
|
|
|
| overlay.classList.remove('active');
|
| showToast('<i class="fas fa-exclamation-circle"></i> المادة غير متاحة، يرجى اختيار مادة من القائمة', 'error', 4000);
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| return;
|
| }
|
|
|
| if (!subjectInfo.accessible) {
|
| overlay.classList.remove('active');
|
| showToast(`<i class="fas fa-lock"></i> يجب شراء مادة "${subjectInfo.name}" للوصول إليها`, 'error', 4000);
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| return;
|
| }
|
|
|
|
|
| selectedSubjectId = subjectFromUrl;
|
| selectedSubjectName = subjectInfo.name;
|
|
|
|
|
|
|
| const selectResponse = await safeFetch('/select-subject', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ subject: subjectFromUrl })
|
| });
|
|
|
| if (!selectResponse) {
|
| overlay.classList.remove('active');
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| return;
|
| }
|
|
|
| const selectData = await selectResponse.json();
|
|
|
| if (selectData.error) {
|
| overlay.classList.remove('active');
|
| showToast(`<i class="fas fa-exclamation-circle"></i> ${selectData.error}`, 'error');
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| return;
|
| }
|
|
|
|
|
|
|
| await switchToChatPage(selectData);
|
|
|
| } catch(error) {
|
| console.error('❌ Init error with subject param:', error);
|
| overlay.classList.remove('active');
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| }
|
|
|
| return;
|
| }
|
|
|
|
|
|
|
| try {
|
| const sessionResponse = await safeFetch('/session-info', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({})
|
| });
|
|
|
| if (sessionResponse) {
|
| const sessionData = await sessionResponse.json();
|
|
|
| if (sessionData.subject && !sessionData.error) {
|
| console.log(`🔄 Resuming subject from server session: ${sessionData.subject}`);
|
|
|
|
|
| const meResponse = await safeFetch('/me');
|
| if (meResponse) {
|
| const meData = await meResponse.json();
|
| if (!meData.error) {
|
| currentUserData = meData;
|
| document.getElementById('userDisplayName').textContent = meData.username;
|
| document.getElementById('userTrackBadge').textContent = meData.student_type || '';
|
| document.getElementById('headerUsername').textContent = meData.username;
|
|
|
| const subjects = meData.subjects || [];
|
| const subjectInfo = subjects.find(s => s.id === sessionData.subject);
|
|
|
| if (subjectInfo && subjectInfo.accessible) {
|
| selectedSubjectId = sessionData.subject;
|
| selectedSubjectName = subjectInfo.name;
|
|
|
|
|
|
|
| const overlay = document.getElementById('transitionOverlay');
|
| document.getElementById('transitionText').textContent = 'جاري استعادة المحادثة...';
|
| overlay.classList.add('active');
|
|
|
| const selectResponse = await safeFetch('/select-subject', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ subject: selectedSubjectId })
|
| });
|
|
|
| if (selectResponse) {
|
| const selectData = await selectResponse.json();
|
| if (!selectData.error) {
|
| await switchToChatPage(selectData);
|
| return;
|
| }
|
| }
|
|
|
| overlay.classList.remove('active');
|
| }
|
| }
|
| }
|
| }
|
| }
|
| } catch(e) {
|
| console.warn('Could not check server session on init:', e);
|
| }
|
|
|
|
|
| console.log('📚 No subject found - showing subject selection');
|
| document.getElementById('subjectPage').classList.remove('hidden');
|
| await loadUserAndSubjects();
|
| }
|
|
|
| init();
|
|
|
| console.log('✅ AI Chat Interface loaded');
|
| console.log(`👤 Username: ${USERNAME}`);
|
| console.log('🔐 Auth: Flask Session');
|
| console.log('🔌 SocketIO: Active');
|
| console.log('🎤 Voice Input: Active');
|
| console.log('🖼️ Image Analysis: Active');
|
| console.log('📚 Multi-subject: Active (Per-Subject History)');
|
| console.log('🔄 Streaming: Active');
|
| console.log('📂 Chat History: Active (Isolated Per Subject)');
|
| </script>
|
| </body>
|
| </html> |