|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>{% block title %}TexLab - Professional LaTeX Converter{% endblock %}</title>
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
|
|
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
|
|
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
|
|
<style>
|
|
|
:root {
|
|
|
--primary: #4361ee;
|
|
|
--primary-dark: #3a56d4;
|
|
|
--secondary: #7209b7;
|
|
|
--accent: #4cc9f0;
|
|
|
--success: #4caf50;
|
|
|
--dark: #212529;
|
|
|
--light: #f8f9fa;
|
|
|
--gray: #6c757d;
|
|
|
--border-radius: 12px;
|
|
|
--box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
--transition: all 0.3s ease;
|
|
|
}
|
|
|
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
html, body {
|
|
|
height: 100%;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
|
background-color: #fafbff;
|
|
|
color: #333;
|
|
|
line-height: 1.6;
|
|
|
min-height: 100vh;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|
|
|
|
|
|
.navbar {
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
padding: 1rem 0;
|
|
|
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.navbar .container-fluid {
|
|
|
padding-left: 20px;
|
|
|
padding-right: 20px;
|
|
|
}
|
|
|
|
|
|
.navbar-brand {
|
|
|
font-weight: 700;
|
|
|
font-size: 1.8rem;
|
|
|
color: white !important;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding-left: 100px;
|
|
|
}
|
|
|
|
|
|
.navbar-brand i {
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.navbar-nav {
|
|
|
padding-left: 30px;
|
|
|
}
|
|
|
|
|
|
.navbar-nav .nav-item {
|
|
|
margin-right: 20px;
|
|
|
}
|
|
|
|
|
|
.nav-link {
|
|
|
color: rgba(255, 255, 255, 0.85) !important;
|
|
|
font-weight: 500;
|
|
|
padding: 0.5rem 1rem !important;
|
|
|
border-radius: 50px;
|
|
|
transition: var(--transition);
|
|
|
margin: 0 2px;
|
|
|
}
|
|
|
|
|
|
.nav-link:hover, .nav-link.active {
|
|
|
color: white !important;
|
|
|
background: rgba(255, 255, 255, 0.15) !important;
|
|
|
}
|
|
|
|
|
|
.user-info {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
color: white;
|
|
|
font-weight: 500;
|
|
|
padding: 0.5rem 1rem;
|
|
|
border-radius: 50px;
|
|
|
background: rgba(255, 255, 255, 0.15);
|
|
|
margin-left: 0.5rem;
|
|
|
margin-right: 15px;
|
|
|
}
|
|
|
|
|
|
.user-info i {
|
|
|
margin-right: 0.5rem;
|
|
|
}
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
flex: 1;
|
|
|
padding: 2rem 0;
|
|
|
}
|
|
|
|
|
|
.page-header {
|
|
|
text-align: center;
|
|
|
margin-bottom: 2.5rem;
|
|
|
padding: 0 1rem;
|
|
|
}
|
|
|
|
|
|
.page-title {
|
|
|
font-size: 2.2rem;
|
|
|
font-weight: 800;
|
|
|
margin-bottom: 1rem;
|
|
|
color: var(--dark);
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
-webkit-background-clip: text;
|
|
|
-webkit-text-fill-color: transparent;
|
|
|
}
|
|
|
|
|
|
.page-subtitle {
|
|
|
font-size: 1.1rem;
|
|
|
color: var(--gray);
|
|
|
max-width: 700px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
|
|
|
.feature-card {
|
|
|
background: white;
|
|
|
border-radius: var(--border-radius);
|
|
|
padding: 2rem;
|
|
|
box-shadow: var(--box-shadow);
|
|
|
transition: var(--transition);
|
|
|
height: 100%;
|
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
|
}
|
|
|
|
|
|
.feature-card:hover {
|
|
|
transform: translateY(-5px);
|
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
|
|
|
}
|
|
|
|
|
|
.feature-icon {
|
|
|
width: 70px;
|
|
|
height: 70px;
|
|
|
border-radius: 50%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
margin: 0 auto 1.5rem;
|
|
|
font-size: 1.8rem;
|
|
|
}
|
|
|
|
|
|
.feature-title {
|
|
|
font-size: 1.4rem;
|
|
|
font-weight: 700;
|
|
|
margin-bottom: 1rem;
|
|
|
color: var(--dark);
|
|
|
}
|
|
|
|
|
|
.feature-description {
|
|
|
color: var(--gray);
|
|
|
margin-bottom: 1.5rem;
|
|
|
font-size: 0.95rem;
|
|
|
}
|
|
|
|
|
|
|
|
|
.btn-primary-gradient {
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 10px 22px;
|
|
|
border-radius: 50px;
|
|
|
font-weight: 600;
|
|
|
transition: var(--transition);
|
|
|
font-size: 0.95rem;
|
|
|
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-primary-gradient:hover {
|
|
|
transform: translateY(-3px);
|
|
|
box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-secondary-gradient {
|
|
|
background: linear-gradient(135deg, #f72585 0%, #b5179e 100%);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 10px 22px;
|
|
|
border-radius: 50px;
|
|
|
font-weight: 600;
|
|
|
transition: var(--transition);
|
|
|
font-size: 0.95rem;
|
|
|
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-secondary-gradient:hover {
|
|
|
transform: translateY(-3px);
|
|
|
box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-accent-gradient {
|
|
|
background: linear-gradient(135deg, var(--accent) 0%, #4895ef 100%);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 10px 22px;
|
|
|
border-radius: 50px;
|
|
|
font-weight: 600;
|
|
|
transition: var(--transition);
|
|
|
font-size: 0.95rem;
|
|
|
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-accent-gradient:hover {
|
|
|
transform: translateY(-3px);
|
|
|
box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-dark-gradient {
|
|
|
background: linear-gradient(135deg, #3a0ca3 0%, #250070 100%);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 10px 22px;
|
|
|
border-radius: 50px;
|
|
|
font-weight: 600;
|
|
|
transition: var(--transition);
|
|
|
font-size: 0.95rem;
|
|
|
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
.btn-dark-gradient:hover {
|
|
|
transform: translateY(-3px);
|
|
|
box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
|
|
|
.notification {
|
|
|
position: fixed;
|
|
|
top: 20px;
|
|
|
right: 20px;
|
|
|
padding: 15px 25px;
|
|
|
border-radius: 10px;
|
|
|
background: var(--success);
|
|
|
color: white;
|
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
|
transform: translateX(200%);
|
|
|
transition: transform 0.3s ease;
|
|
|
z-index: 1000;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.notification.show {
|
|
|
transform: translateX(0);
|
|
|
}
|
|
|
|
|
|
|
|
|
.footer {
|
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
color: #cbd5e1;
|
|
|
padding: 3rem 0 2rem;
|
|
|
flex-shrink: 0;
|
|
|
margin-top: auto;
|
|
|
}
|
|
|
|
|
|
.footer-content {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
gap: 2rem;
|
|
|
margin-bottom: 2rem;
|
|
|
text-align: left;
|
|
|
margin-left: -20px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.footer-content > .footer-column:first-child {
|
|
|
text-align: left;
|
|
|
padding-left: 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
.footer-content > .footer-column:nth-child(2) {
|
|
|
padding-left: 0;
|
|
|
}
|
|
|
|
|
|
.footer-column h4 {
|
|
|
color: white;
|
|
|
font-size: 1.2rem;
|
|
|
margin-bottom: 1.5rem;
|
|
|
position: relative;
|
|
|
padding-bottom: 0.5rem;
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
.footer-column h4::after {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
bottom: 0;
|
|
|
left: 0;
|
|
|
width: 50px;
|
|
|
height: 3px;
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
|
|
|
.footer-column p {
|
|
|
margin-bottom: 1rem;
|
|
|
line-height: 1.7;
|
|
|
}
|
|
|
|
|
|
.footer-links {
|
|
|
list-style: none;
|
|
|
padding: 0;
|
|
|
}
|
|
|
|
|
|
.footer-links li {
|
|
|
margin-bottom: 0.8rem;
|
|
|
}
|
|
|
|
|
|
.footer-links a {
|
|
|
color: #94a3b8;
|
|
|
text-decoration: none;
|
|
|
transition: var(--transition);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.footer-links a:hover {
|
|
|
color: white;
|
|
|
transform: translateX(5px);
|
|
|
}
|
|
|
|
|
|
.footer-links a i {
|
|
|
margin-right: 10px;
|
|
|
font-size: 0.8rem;
|
|
|
}
|
|
|
|
|
|
.social-links {
|
|
|
display: flex;
|
|
|
gap: 1rem;
|
|
|
margin-top: 1rem;
|
|
|
}
|
|
|
|
|
|
.social-link {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 40px;
|
|
|
height: 40px;
|
|
|
border-radius: 50%;
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
color: white;
|
|
|
transition: var(--transition);
|
|
|
}
|
|
|
|
|
|
.social-link:hover {
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
transform: translateY(-3px);
|
|
|
}
|
|
|
|
|
|
.copyright {
|
|
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
padding-top: 2rem;
|
|
|
text-align: left;
|
|
|
color: #94a3b8;
|
|
|
font-size: 0.9rem;
|
|
|
max-width: 800px;
|
|
|
margin: 0 auto 0 0;
|
|
|
}
|
|
|
|
|
|
.copyright-content {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 0.5rem;
|
|
|
}
|
|
|
|
|
|
.copyright-line {
|
|
|
margin: 0;
|
|
|
}
|
|
|
|
|
|
.copyright a {
|
|
|
color: #94a3b8;
|
|
|
text-decoration: none;
|
|
|
}
|
|
|
|
|
|
.copyright a:hover {
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-toggle {
|
|
|
position: fixed;
|
|
|
bottom: 20px;
|
|
|
right: 20px;
|
|
|
width: 60px;
|
|
|
height: 60px;
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
border-radius: 50%;
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
color: white;
|
|
|
font-size: 1.5rem;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s ease;
|
|
|
z-index: 1001;
|
|
|
border: none;
|
|
|
}
|
|
|
|
|
|
.chat-toggle:hover {
|
|
|
transform: scale(1.1);
|
|
|
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.15);
|
|
|
}
|
|
|
|
|
|
|
|
|
#latexToTextToggle {
|
|
|
bottom: 100px;
|
|
|
width: 60px;
|
|
|
height: 60px;
|
|
|
background: linear-gradient(135deg, #4cc9f0 0%, #4895ef 100%);
|
|
|
font-size: 1.5rem;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-panel {
|
|
|
position: fixed;
|
|
|
bottom: 20px;
|
|
|
right: 20px;
|
|
|
width: 500px;
|
|
|
background: white;
|
|
|
border-radius: 12px;
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
transform: translateX(200%);
|
|
|
transition: transform 0.3s ease;
|
|
|
z-index: 1000;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
overflow: hidden;
|
|
|
max-height: 90vh;
|
|
|
}
|
|
|
|
|
|
.chat-panel.active {
|
|
|
transform: translateX(0);
|
|
|
}
|
|
|
|
|
|
.chat-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 1rem;
|
|
|
border-bottom: 1px solid #e9ecef;
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.chat-header h5 {
|
|
|
margin: 0;
|
|
|
font-size: 1.3rem;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.chat-close {
|
|
|
background: none;
|
|
|
border: none;
|
|
|
color: white;
|
|
|
font-size: 1.5rem;
|
|
|
cursor: pointer;
|
|
|
transition: color 0.3s ease;
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
.chat-close:hover {
|
|
|
background: rgba(255, 255, 255, 0.2);
|
|
|
}
|
|
|
|
|
|
.chat-messages {
|
|
|
flex: 1;
|
|
|
padding: 1rem;
|
|
|
overflow-y: auto;
|
|
|
max-height: 65vh;
|
|
|
}
|
|
|
|
|
|
.chat-messages .welcome-message {
|
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
|
border-radius: 8px;
|
|
|
padding: 20px;
|
|
|
margin-bottom: 1rem;
|
|
|
text-align: center;
|
|
|
border: 1px solid #dee2e6;
|
|
|
}
|
|
|
|
|
|
.chat-messages .welcome-message h6 {
|
|
|
color: #4361ee;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 1.4rem;
|
|
|
}
|
|
|
|
|
|
.chat-messages .welcome-message p {
|
|
|
color: #495057;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 1rem;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
|
|
|
.chat-messages .welcome-message ul {
|
|
|
text-align: left;
|
|
|
padding-left: 20px;
|
|
|
margin: 15px 0;
|
|
|
color: #495057;
|
|
|
}
|
|
|
|
|
|
.chat-messages .welcome-message li {
|
|
|
margin-bottom: 10px;
|
|
|
line-height: 1.5;
|
|
|
}
|
|
|
|
|
|
.chat-messages .welcome-message strong {
|
|
|
color: #212529;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message {
|
|
|
margin-bottom: 1rem;
|
|
|
padding: 1rem;
|
|
|
border-radius: 8px;
|
|
|
background: #f8f9fa;
|
|
|
color: var(--dark);
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message.user-message {
|
|
|
background: #e9ecef;
|
|
|
color: var(--dark);
|
|
|
}
|
|
|
|
|
|
.chat-messages .message.user-message::after {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
bottom: -10px;
|
|
|
right: 10px;
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
border-left: 10px solid transparent;
|
|
|
border-right: 10px solid transparent;
|
|
|
border-top: 10px solid #e9ecef;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message.assistant-message::after {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
bottom: -10px;
|
|
|
left: 10px;
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
border-left: 10px solid #f8f9fa;
|
|
|
border-right: 10px solid transparent;
|
|
|
border-top: 10px solid transparent;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .message-info {
|
|
|
font-size: 0.8rem;
|
|
|
color: var(--gray);
|
|
|
margin-top: 0.5rem;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .message-timestamp {
|
|
|
font-size: 0.75rem;
|
|
|
color: #6c757d;
|
|
|
text-align: right;
|
|
|
margin-top: 8px;
|
|
|
font-style: italic;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .chat-code-block {
|
|
|
background: #f8f9fa;
|
|
|
border: 1px solid #dee2e6;
|
|
|
border-radius: 8px;
|
|
|
padding: 15px;
|
|
|
min-height: 80px;
|
|
|
font-size: 1.1rem;
|
|
|
font-family: 'Segoe UI', sans-serif;
|
|
|
white-space: pre-wrap;
|
|
|
overflow-y: auto;
|
|
|
max-height: 150px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .chat-code-inline {
|
|
|
background: #f8f9fa;
|
|
|
border: 1px solid #dee2e6;
|
|
|
border-radius: 4px;
|
|
|
padding: 0 4px;
|
|
|
font-size: 0.95rem;
|
|
|
font-family: 'Segoe UI', sans-serif;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .chat-image {
|
|
|
max-width: 70%;
|
|
|
border-radius: 8px;
|
|
|
margin-top: 1rem;
|
|
|
display: block;
|
|
|
margin-left: auto;
|
|
|
margin-right: auto;
|
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .chat-image-preview {
|
|
|
max-width: 70%;
|
|
|
border-radius: 8px;
|
|
|
margin-top: 1rem;
|
|
|
opacity: 0.7;
|
|
|
display: block;
|
|
|
margin-left: auto;
|
|
|
margin-right: auto;
|
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .loading {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
margin-top: 1rem;
|
|
|
padding: 20px;
|
|
|
background: #f8f9fa;
|
|
|
border-radius: 8px;
|
|
|
border: 1px dashed #dee2e6;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .loading .loading-dots {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .loading .loading-dot {
|
|
|
width: 12px;
|
|
|
height: 12px;
|
|
|
background: var(--primary);
|
|
|
border-radius: 50%;
|
|
|
animation: loading 1.2s infinite ease-in-out;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .loading .loading-dot:nth-child(2) {
|
|
|
animation-delay: 0.4s;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .loading .loading-dot:nth-child(3) {
|
|
|
animation-delay: 0.8s;
|
|
|
}
|
|
|
|
|
|
.chat-messages .message .loading .loading-text {
|
|
|
font-size: 0.95rem;
|
|
|
color: var(--gray);
|
|
|
margin-top: 1rem;
|
|
|
font-style: italic;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
@keyframes loading {
|
|
|
0%, 100% {
|
|
|
transform: scale(0);
|
|
|
}
|
|
|
50% {
|
|
|
transform: scale(1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.chat-input {
|
|
|
padding: 1rem;
|
|
|
border-top: 1px solid #e9ecef;
|
|
|
background-color: #f8f9fa;
|
|
|
}
|
|
|
|
|
|
.chat-input .input-group {
|
|
|
display: flex;
|
|
|
gap: 0;
|
|
|
}
|
|
|
|
|
|
.chat-input .input-group textarea {
|
|
|
flex: 1;
|
|
|
border-radius: 8px 0 0 8px;
|
|
|
border: 1px solid #dee2e6;
|
|
|
padding: 15px;
|
|
|
font-size: 1rem;
|
|
|
resize: vertical;
|
|
|
min-height: 60px;
|
|
|
font-family: 'Segoe UI', sans-serif;
|
|
|
}
|
|
|
|
|
|
.chat-input .input-group textarea:focus {
|
|
|
outline: none;
|
|
|
border-color: #4361ee;
|
|
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
|
|
|
}
|
|
|
|
|
|
.chat-input .input-group button {
|
|
|
border-radius: 0 8px 8px 0;
|
|
|
padding: 15px;
|
|
|
font-size: 1rem;
|
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
|
|
border: none;
|
|
|
color: white;
|
|
|
cursor: pointer;
|
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
|
}
|
|
|
|
|
|
.chat-input .input-group button:hover {
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.chat-input .input-group button:disabled {
|
|
|
background: #adb5bd;
|
|
|
cursor: not-allowed;
|
|
|
transform: none;
|
|
|
box-shadow: none;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
margin-top: 12px;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper label {
|
|
|
cursor: pointer;
|
|
|
padding: 8px 16px;
|
|
|
border-radius: 6px;
|
|
|
font-size: 0.9rem;
|
|
|
transition: all 0.2s;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 5px;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper label:hover {
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper .btn-outline-primary {
|
|
|
border: 1px solid #4361ee;
|
|
|
color: #4361ee;
|
|
|
background: white;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper .btn-outline-primary:hover {
|
|
|
background: #4361ee;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper .btn-outline-danger {
|
|
|
border: 1px solid #f72585;
|
|
|
color: #f72585;
|
|
|
background: white;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper .btn-outline-danger:hover {
|
|
|
background: #f72585;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.chat-input .file-upload-wrapper input {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
|
|
|
#latexToTextPanel .welcome-message {
|
|
|
background: #f8f9fa;
|
|
|
border-radius: 8px;
|
|
|
padding: 15px;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
#latexToTextPanel .welcome-message h6 {
|
|
|
color: #4361ee;
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
#latexToTextPanel .welcome-message ul {
|
|
|
padding-left: 20px;
|
|
|
margin: 10px 0;
|
|
|
}
|
|
|
|
|
|
#latexToTextPanel .welcome-message code {
|
|
|
background: #e9ecef;
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 4px;
|
|
|
font-size: 0.85rem;
|
|
|
}
|
|
|
|
|
|
#latexInput {
|
|
|
transition: border-color 0.2s, box-shadow 0.2s;
|
|
|
padding: 15px;
|
|
|
font-size: 1rem;
|
|
|
min-height: 120px;
|
|
|
}
|
|
|
|
|
|
#latexInput:focus {
|
|
|
outline: none;
|
|
|
border-color: #4361ee;
|
|
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
|
|
|
}
|
|
|
|
|
|
#convertedTextOutput {
|
|
|
background: white;
|
|
|
border: 1px solid #e9ecef;
|
|
|
border-radius: 8px;
|
|
|
padding: 20px;
|
|
|
min-height: 120px;
|
|
|
font-size: 1.2rem;
|
|
|
font-family: 'Segoe UI', sans-serif;
|
|
|
white-space: pre-wrap;
|
|
|
overflow-y: auto;
|
|
|
max-height: 200px;
|
|
|
font-size: 1.3rem;
|
|
|
}
|
|
|
|
|
|
#convertedTextOutput:hover {
|
|
|
background-color: #f8f9fa;
|
|
|
}
|
|
|
|
|
|
#charCount {
|
|
|
transition: color 0.2s;
|
|
|
}
|
|
|
|
|
|
#charCount.warning {
|
|
|
color: #f72585;
|
|
|
}
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
.footer-content {
|
|
|
grid-template-columns: 1fr;
|
|
|
gap: 2rem;
|
|
|
}
|
|
|
|
|
|
.copyright-content {
|
|
|
flex-direction: column;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
|
|
|
.chat-panel {
|
|
|
width: 90vw;
|
|
|
right: 5vw;
|
|
|
bottom: 10px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
width: 32px;
|
|
|
height: 32px;
|
|
|
border-radius: 50%;
|
|
|
margin-right: 8px;
|
|
|
object-fit: cover;
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
{% block extra_css %}{% endblock %}
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
{% if request.endpoint != 'auth.login' %}
|
|
|
<nav class="navbar navbar-expand-lg navbar-dark">
|
|
|
<div class="container-fluid">
|
|
|
<a class="navbar-brand" href="/">
|
|
|
<i class="fas fa-calculator"></i>
|
|
|
<span>TexLab</span>
|
|
|
</a>
|
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
|
<span class="navbar-toggler-icon"></span>
|
|
|
</button>
|
|
|
<div class="collapse navbar-collapse" id="navbarNav">
|
|
|
<ul class="navbar-nav me-auto">
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/math">Math</a>
|
|
|
</li>
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/scribble">Scribble</a>
|
|
|
</li>
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/camera">Camera</a>
|
|
|
</li>
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/table">Table</a>
|
|
|
</li>
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/pdffly">PDFly</a>
|
|
|
</li>
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/graph">Graph</a>
|
|
|
</li>
|
|
|
<li class="nav-item">
|
|
|
<a class="nav-link" href="/pricing">Pricing</a>
|
|
|
</li>
|
|
|
</ul>
|
|
|
<div class="d-flex align-items-center" style="margin-right: 15px;">
|
|
|
<div id="authContainer">
|
|
|
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</nav>
|
|
|
{% endif %}
|
|
|
|
|
|
<div class="container main-content">
|
|
|
{% block content %}{% endblock %}
|
|
|
</div>
|
|
|
|
|
|
{% if request.endpoint != 'auth.login' %}
|
|
|
{% if request.path == '/' %}
|
|
|
<footer class="site-footer">
|
|
|
|
|
|
<footer class="footer" style="width: 100%; background: #1e293b; color: #fff; padding: 2rem 1rem;">
|
|
|
<div class="container">
|
|
|
<div class="footer-content">
|
|
|
<div class="footer-column">
|
|
|
<h4>About TexLab</h4>
|
|
|
<p>
|
|
|
TexLab is a cutting-edge platform that transforms handwritten mathematical expressions,
|
|
|
scribbles, tables, and camera captures into clean, editable LaTeX code using advanced AI models.
|
|
|
</p>
|
|
|
<div class="social-links">
|
|
|
<a href="#" class="social-link"><i class="fab fa-twitter"></i></a>
|
|
|
<a href="#" class="social-link"><i class="fab fa-facebook-f"></i></a>
|
|
|
<a href="#" class="social-link"><i class="fab fa-linkedin-in"></i></a>
|
|
|
<a href="#" class="social-link"><i class="fab fa-github"></i></a>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="footer-column">
|
|
|
<h4>Quick Links</h4>
|
|
|
<ul class="footer-links">
|
|
|
<li><a href="/"><i class="fas fa-chevron-right"></i> Home</a></li>
|
|
|
<li><a href="/math"><i class="fas fa-chevron-right"></i> Math Equations</a></li>
|
|
|
<li><a href="/scribble"><i class="fas fa-chevron-right"></i> Scribble Converter</a></li>
|
|
|
<li><a href="/camera"><i class="fas fa-chevron-right"></i> Camera Capture</a></li>
|
|
|
<li><a href="/table"><i class="fas fa-chevron-right"></i> Table Detection</a></li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
|
|
|
<div class="footer-column">
|
|
|
<h4>Resources</h4>
|
|
|
<ul class="footer-links">
|
|
|
<li><a href="/help"><i class="fas fa-chevron-right"></i> Documentation</a></li>
|
|
|
<li><a href="/tutorials"><i class="fas fa-chevron-right"></i> Tutorials</a></li>
|
|
|
<li><a href="/api"><i class="fas fa-chevron-right"></i> API Reference</a></li>
|
|
|
<li><a href="/blog"><i class="fas fa-chevron-right"></i> Blog</a></li>
|
|
|
<li><a href="/support"><i class="fas fa-chevron-right"></i> Support Center</a></li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
|
|
|
<div class="footer-column">
|
|
|
<h4>Legal</h4>
|
|
|
<ul class="footer-links">
|
|
|
<li><a href="/privacy"><i class="fas fa-chevron-right"></i> Privacy Policy</a></li>
|
|
|
<li><a href="/terms"><i class="fas fa-chevron-right"></i> Terms of Service</a></li>
|
|
|
<li><a href="/cookies"><i class="fas fa-chevron-right"></i> Cookie Policy</a></li>
|
|
|
<li><a href="/gdpr"><i class="fas fa-chevron-right"></i> GDPR Compliance</a></li>
|
|
|
<li><a href="/contact"><i class="fas fa-chevron-right"></i> Contact Us</a></li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="copyright">
|
|
|
<div class="copyright-content">
|
|
|
<p class="copyright-line">© 2025 TexLab. All rights reserved. Transforming handwritten content into beautiful LaTeX since 2025.</p>
|
|
|
<p class="copyright-line">Designed with <i class="fas fa-heart" style="color: #f72585;"></i> for mathematicians, scientists, and educators worldwide.</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</footer>
|
|
|
</footer>
|
|
|
{% endif %}
|
|
|
{% endif %}
|
|
|
|
|
|
{% if request.endpoint != 'auth.login' %}
|
|
|
|
|
|
<button class="chat-toggle" id="chatToggle">
|
|
|
<i class="fas fa-robot"></i>
|
|
|
</button>
|
|
|
|
|
|
|
|
|
<button class="chat-toggle" id="latexToTextToggle" style="bottom: 100px;">
|
|
|
<i class="fas fa-exchange-alt"></i>
|
|
|
</button>
|
|
|
|
|
|
|
|
|
<div class="chat-panel" id="chatPanel">
|
|
|
<div class="chat-header">
|
|
|
<h5><i class="fas fa-robot me-2"></i>TexLab Assistant</h5>
|
|
|
<button class="chat-close" id="chatClose">
|
|
|
<i class="fas fa-times"></i>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-messages" id="chatMessages">
|
|
|
<div class="welcome-message">
|
|
|
<h6>Welcome to TexLab Assistant!</h6>
|
|
|
<p>I can help you with LaTeX, mathematics, and document conversion.</p>
|
|
|
<p>✨ <strong>New Features:</strong></p>
|
|
|
<ul>
|
|
|
<li>Upload images of math equations or tables for LaTeX conversion</li>
|
|
|
<li>Upload PDF files for content extraction</li>
|
|
|
</ul>
|
|
|
<p>Try asking: "How do I write a fraction in LaTeX?" or upload an image/PDF!</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-input">
|
|
|
<div class="input-group mb-2">
|
|
|
<textarea id="messageInput" placeholder="Ask me anything about LaTeX or math..." class="form-control" style="border-radius: 8px 0 0 8px;"></textarea>
|
|
|
<button class="chat-send btn btn-primary" id="sendButton" disabled style="border-radius: 0 8px 8px 0;">
|
|
|
<i class="fas fa-paper-plane"></i>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="d-flex justify-content-center">
|
|
|
<div class="file-upload-wrapper">
|
|
|
<label for="imageUpload" class="btn btn-sm btn-outline-primary mb-0" style="font-size: 0.8rem; padding: 0.2rem 0.4rem;">
|
|
|
<i class="fas fa-image"></i>
|
|
|
</label>
|
|
|
<input type="file" id="imageUpload" accept="image/*" class="d-none">
|
|
|
|
|
|
<label for="pdfUpload" class="btn btn-sm btn-outline-danger mb-0 ms-1" style="font-size: 0.8rem; padding: 0.2rem 0.4rem;">
|
|
|
<i class="fas fa-file-pdf"></i>
|
|
|
</label>
|
|
|
<input type="file" id="pdfUpload" accept=".pdf" class="d-none">
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="chat-panel" id="latexToTextPanel" style="bottom: 140px;">
|
|
|
<div class="chat-header">
|
|
|
<h5><i class="fas fa-exchange-alt me-2"></i>LaTeX to Rendered Equation</h5>
|
|
|
<button class="chat-close" id="latexToTextClose">
|
|
|
<i class="fas fa-times"></i>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-messages" id="latexToTextMessages">
|
|
|
<div class="welcome-message">
|
|
|
<h6>Convert LaTeX to Rendered Equation</h6>
|
|
|
<p>Enter LaTeX code below to convert it to a rendered mathematical equation.</p>
|
|
|
<p><strong>Examples:</strong></p>
|
|
|
<ul style="text-align: left; font-size: 0.9rem;">
|
|
|
<li><code>\int_{0}^{\infty} e^{-x^2} dx</code></li>
|
|
|
<li><code>\frac{\partial f}{\partial x} + \frac{\partial f}{\partial y}</code></li>
|
|
|
<li><code>\sum_{i=1}^{n} i^2 = \frac{n(n+1)(2n+1)}{6}</code></li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div style="padding: 1.5rem; border-top: 1px solid #e9ecef;">
|
|
|
<textarea id="latexInput" placeholder="Enter LaTeX code here...
|
|
|
Example: \int_{0}^{\infty} e^{-x^2} dx" style="width: 100%; height: 60px; padding: 12px; border-radius: 8px; border: 1px solid #ddd; resize: vertical; font-family: 'Consolas', monospace; font-size: 1rem;"></textarea>
|
|
|
<div style="display: flex; gap: 15px; margin-top: 15px;">
|
|
|
<button class="btn btn-primary-gradient" id="convertLatexBtn" style="flex: 1; padding: 12px; font-size: 1.1rem;">
|
|
|
<i class="fas fa-exchange-alt me-1"></i>Render Equation
|
|
|
</button>
|
|
|
<button class="btn btn-neutral" id="copyTextBtn" style="flex: 1; padding: 12px; font-size: 1.1rem;">
|
|
|
<i class="fas fa-copy me-1"></i>Copy LaTeX
|
|
|
</button>
|
|
|
<button class="btn btn-outline-secondary" id="clearTextBtn" style="flex: 1; padding: 12px; font-size: 1.1rem; border-radius: 6px;">
|
|
|
<i class="fas fa-trash me-1"></i>Clear
|
|
|
</button>
|
|
|
</div>
|
|
|
<div style="margin-top: 20px;">
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
|
|
<h6 style="margin-bottom: 0; color: #4361ee;">Rendered Equation:</h6>
|
|
|
<span id="charCount" style="font-size: 0.9rem; color: #6c757d;">0 characters</span>
|
|
|
</div>
|
|
|
<div id="convertedTextOutput" style="background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 12px; min-height: 60px; font-size: 1.3rem; font-family: 'Segoe UI', sans-serif; white-space: pre-wrap; overflow-y: auto; max-height: 120px; text-align: center;">
|
|
|
<div id="math-output" style="font-size: 1.5rem;">Your rendered equation will appear here...</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="notification" id="notification">
|
|
|
<i class="fas fa-check-circle me-2"></i>LaTeX code copied to clipboard!
|
|
|
</div>
|
|
|
{% endif %}
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
{% block extra_js %}{% endblock %}
|
|
|
<script>
|
|
|
|
|
|
const isLoginPage = document.querySelector('.login-container') !== null;
|
|
|
|
|
|
if (!isLoginPage) {
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
fetch('/auth/user')
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
const authContainer = document.getElementById('authContainer');
|
|
|
if (data.authenticated) {
|
|
|
|
|
|
if (data.user.email && data.user.email.includes('@')) {
|
|
|
|
|
|
authContainer.innerHTML = `
|
|
|
<div class="user-info">
|
|
|
<i class="fab fa-google"></i>
|
|
|
<span>${data.user.name}</span>
|
|
|
</div>
|
|
|
<a href="/auth/logout" class="btn btn-signin ms-2">
|
|
|
<i class="fas fa-sign-out-alt me-1"></i>Sign Out
|
|
|
</a>
|
|
|
`;
|
|
|
} else {
|
|
|
|
|
|
authContainer.innerHTML = `
|
|
|
<div class="user-info">
|
|
|
<i class="fas fa-user"></i>
|
|
|
<span>Guest: ${data.user.name}</span>
|
|
|
</div>
|
|
|
<a href="/auth/logout" class="btn btn-signin ms-2">
|
|
|
<i class="fas fa-sign-out-alt me-1"></i>Sign Out
|
|
|
</a>
|
|
|
`;
|
|
|
}
|
|
|
} else {
|
|
|
authContainer.innerHTML = `
|
|
|
<a href="/auth/login" class="btn btn-signin">
|
|
|
<i class="fas fa-sign-in-alt me-1"></i>Sign In
|
|
|
</a>
|
|
|
`;
|
|
|
}
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('Error checking auth status:', error);
|
|
|
|
|
|
document.getElementById('authContainer').innerHTML = `
|
|
|
<a href="/auth/login" class="btn btn-signin">
|
|
|
<i class="fas fa-sign-in-alt me-1"></i>Sign In
|
|
|
</a>
|
|
|
`;
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
const chatToggle = document.getElementById('chatToggle');
|
|
|
const chatPanel = document.getElementById('chatPanel');
|
|
|
const chatClose = document.getElementById('chatClose');
|
|
|
const chatMessages = document.getElementById('chatMessages');
|
|
|
const messageInput = document.getElementById('messageInput');
|
|
|
const sendButton = document.getElementById('sendButton');
|
|
|
const imageUpload = document.getElementById('imageUpload');
|
|
|
const pdfUpload = document.getElementById('pdfUpload');
|
|
|
|
|
|
|
|
|
chatToggle.addEventListener('click', () => {
|
|
|
chatPanel.classList.toggle('active');
|
|
|
|
|
|
if (chatPanel.classList.contains('active')) {
|
|
|
chatToggle.style.display = 'none';
|
|
|
|
|
|
const latexToTextToggle = document.getElementById('latexToTextToggle');
|
|
|
if (latexToTextToggle) {
|
|
|
latexToTextToggle.style.display = 'none';
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
chatToggle.style.display = 'flex';
|
|
|
|
|
|
const latexToTextToggle = document.getElementById('latexToTextToggle');
|
|
|
if (latexToTextToggle) {
|
|
|
latexToTextToggle.style.display = 'flex';
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
chatClose.addEventListener('click', () => {
|
|
|
chatPanel.classList.remove('active');
|
|
|
|
|
|
chatToggle.style.display = 'flex';
|
|
|
|
|
|
const latexToTextToggle = document.getElementById('latexToTextToggle');
|
|
|
if (latexToTextToggle) {
|
|
|
latexToTextToggle.style.display = 'flex';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
if (chatPanel.classList.contains('active') &&
|
|
|
!chatPanel.contains(e.target) &&
|
|
|
e.target !== chatToggle &&
|
|
|
!chatToggle.contains(e.target)) {
|
|
|
chatPanel.classList.remove('active');
|
|
|
chatToggle.style.display = 'flex';
|
|
|
|
|
|
const latexToTextToggle = document.getElementById('latexToTextToggle');
|
|
|
if (latexToTextToggle) {
|
|
|
latexToTextToggle.style.display = 'flex';
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
const latexToTextToggle = document.getElementById('latexToTextToggle');
|
|
|
const latexToTextPanel = document.getElementById('latexToTextPanel');
|
|
|
const latexToTextClose = document.getElementById('latexToTextClose');
|
|
|
|
|
|
|
|
|
if (latexToTextToggle && latexToTextPanel && latexToTextClose) {
|
|
|
|
|
|
function showLaTeXToggle() {
|
|
|
latexToTextToggle.style.display = 'flex';
|
|
|
}
|
|
|
|
|
|
|
|
|
function hideLaTeXToggle() {
|
|
|
latexToTextToggle.style.display = 'none';
|
|
|
}
|
|
|
|
|
|
|
|
|
function openLaTeXPanel() {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
latexToTextPanel.classList.add('active');
|
|
|
|
|
|
latexToTextPanel.offsetHeight;
|
|
|
}, 10);
|
|
|
|
|
|
hideLaTeXToggle();
|
|
|
|
|
|
chatPanel.classList.remove('active');
|
|
|
|
|
|
if (chatToggle) {
|
|
|
chatToggle.style.display = 'none';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function closeLaTeXPanel() {
|
|
|
latexToTextPanel.classList.remove('active');
|
|
|
showLaTeXToggle();
|
|
|
|
|
|
if (chatToggle) {
|
|
|
chatToggle.style.display = 'flex';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function showChatToggle() {
|
|
|
if (chatToggle) {
|
|
|
chatToggle.style.display = 'flex';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
latexToTextToggle.addEventListener('click', () => {
|
|
|
if (latexToTextPanel.classList.contains('active')) {
|
|
|
closeLaTeXPanel();
|
|
|
} else {
|
|
|
openLaTeXPanel();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
latexToTextClose.addEventListener('click', closeLaTeXPanel);
|
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
if (latexToTextPanel.classList.contains('active') &&
|
|
|
!latexToTextPanel.contains(e.target) &&
|
|
|
e.target !== latexToTextToggle &&
|
|
|
!latexToTextToggle.contains(e.target)) {
|
|
|
closeLaTeXPanel();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
messageInput.addEventListener('input', function() {
|
|
|
this.style.height = 'auto';
|
|
|
this.style.height = (this.scrollHeight > 120 ? 120 : this.scrollHeight) + 'px';
|
|
|
});
|
|
|
|
|
|
|
|
|
messageInput.addEventListener('input', function() {
|
|
|
sendButton.disabled = !this.value.trim();
|
|
|
});
|
|
|
|
|
|
|
|
|
messageInput.addEventListener('keydown', function(e) {
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
e.preventDefault();
|
|
|
if (!sendButton.disabled) {
|
|
|
sendMessage();
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
sendButton.addEventListener('click', sendMessage);
|
|
|
|
|
|
|
|
|
const sessionId = 'session_' + Date.now();
|
|
|
|
|
|
|
|
|
imageUpload.addEventListener('change', function(e) {
|
|
|
if (this.files && this.files[0]) {
|
|
|
const file = this.files[0];
|
|
|
if (!file.type.match('image.*')) {
|
|
|
alert('Please select an image file (PNG, JPG, JPEG)');
|
|
|
this.value = '';
|
|
|
return;
|
|
|
}
|
|
|
uploadFile(file, 'image');
|
|
|
this.value = '';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
pdfUpload.addEventListener('change', function(e) {
|
|
|
if (this.files && this.files[0]) {
|
|
|
const file = this.files[0];
|
|
|
if (file.type !== 'application/pdf') {
|
|
|
alert('Please select a PDF file');
|
|
|
this.value = '';
|
|
|
return;
|
|
|
}
|
|
|
uploadFile(file, 'pdf');
|
|
|
this.value = '';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
async function uploadFile(file, type) {
|
|
|
|
|
|
let messageContent = `📁 Uploading ${type} file: ${file.name}`;
|
|
|
addMessageToChat(messageContent, 'user');
|
|
|
|
|
|
|
|
|
if (type === 'image' && file.type.match('image.*')) {
|
|
|
const reader = new FileReader();
|
|
|
reader.onload = function(e) {
|
|
|
|
|
|
const imgPreview = document.createElement('img');
|
|
|
imgPreview.src = e.target.result;
|
|
|
imgPreview.className = 'chat-image-preview';
|
|
|
imgPreview.alt = file.name;
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
const userMessages = document.querySelectorAll('.user-message');
|
|
|
if (userMessages.length > 0) {
|
|
|
const lastUserMessage = userMessages[userMessages.length - 1];
|
|
|
|
|
|
if (lastUserMessage.textContent.includes(file.name)) {
|
|
|
lastUserMessage.insertBefore(imgPreview, lastUserMessage.lastChild);
|
|
|
}
|
|
|
}
|
|
|
}, 100);
|
|
|
};
|
|
|
reader.readAsDataURL(file);
|
|
|
}
|
|
|
|
|
|
|
|
|
const loadingElement = document.createElement('div');
|
|
|
loadingElement.className = 'loading';
|
|
|
loadingElement.id = 'loadingIndicator';
|
|
|
loadingElement.innerHTML = `
|
|
|
<div class="loading-dots">
|
|
|
<div class="loading-dot"></div>
|
|
|
<div class="loading-dot"></div>
|
|
|
<div class="loading-dot"></div>
|
|
|
</div>
|
|
|
<div class="loading-text">Processing your ${type} file... This may take a moment.</div>
|
|
|
`;
|
|
|
chatMessages.appendChild(loadingElement);
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
|
|
|
|
try {
|
|
|
const formData = new FormData();
|
|
|
formData.append(type, file);
|
|
|
|
|
|
const response = await fetch('/chatbot/chat', {
|
|
|
method: 'POST',
|
|
|
body: formData
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
document.getElementById('loadingIndicator').remove();
|
|
|
|
|
|
if (data.success) {
|
|
|
addMessageToChat(data.response, 'assistant');
|
|
|
|
|
|
|
|
|
if (data.latex) {
|
|
|
|
|
|
const latexContainer = document.createElement('div');
|
|
|
latexContainer.className = 'chat-code-block';
|
|
|
latexContainer.style.display = 'flex';
|
|
|
latexContainer.style.justifyContent = 'space-between';
|
|
|
latexContainer.style.alignItems = 'center';
|
|
|
latexContainer.style.gap = '10px';
|
|
|
|
|
|
|
|
|
const latexText = document.createElement('div');
|
|
|
latexText.textContent = data.latex;
|
|
|
latexText.style.flex = '1';
|
|
|
latexText.style.overflowX = 'auto';
|
|
|
latexText.style.wordBreak = 'break-all';
|
|
|
latexContainer.appendChild(latexText);
|
|
|
|
|
|
|
|
|
const copyBtn = document.createElement('button');
|
|
|
copyBtn.className = 'btn btn-sm btn-outline-primary';
|
|
|
copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
|
|
|
copyBtn.style.whiteSpace = 'nowrap';
|
|
|
copyBtn.style.flexShrink = '0';
|
|
|
copyBtn.onclick = function() {
|
|
|
navigator.clipboard.writeText(data.latex).then(() => {
|
|
|
showNotification('LaTeX code copied to clipboard!');
|
|
|
}).catch(err => {
|
|
|
|
|
|
const textArea = document.createElement('textarea');
|
|
|
textArea.value = data.latex;
|
|
|
document.body.appendChild(textArea);
|
|
|
textArea.select();
|
|
|
try {
|
|
|
document.execCommand('copy');
|
|
|
showNotification('LaTeX code copied to clipboard!');
|
|
|
} catch (err) {
|
|
|
showNotification('Failed to copy. Please try again.');
|
|
|
}
|
|
|
document.body.removeChild(textArea);
|
|
|
});
|
|
|
};
|
|
|
latexContainer.appendChild(copyBtn);
|
|
|
|
|
|
const lastMessage = chatMessages.lastChild;
|
|
|
lastMessage.insertBefore(latexContainer, lastMessage.lastChild);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (data.image) {
|
|
|
const imgElement = document.createElement('img');
|
|
|
imgElement.src = data.image;
|
|
|
imgElement.className = 'chat-image';
|
|
|
imgElement.alt = 'Processed image result';
|
|
|
|
|
|
const lastMessage = chatMessages.lastChild;
|
|
|
lastMessage.insertBefore(imgElement, lastMessage.lastChild);
|
|
|
}
|
|
|
} else {
|
|
|
addMessageToChat(`❌ ${data.response || 'Error processing file'}`, 'assistant');
|
|
|
}
|
|
|
} catch (error) {
|
|
|
|
|
|
if (document.getElementById('loadingIndicator')) {
|
|
|
document.getElementById('loadingIndicator').remove();
|
|
|
}
|
|
|
console.error('Upload error:', error);
|
|
|
addMessageToChat('❌ Sorry, I encountered an error processing your file. Please try again.', 'assistant');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async function sendMessage() {
|
|
|
const message = messageInput.value.trim();
|
|
|
if (!message) return;
|
|
|
|
|
|
|
|
|
messageInput.value = '';
|
|
|
messageInput.style.height = '44px';
|
|
|
sendButton.disabled = true;
|
|
|
|
|
|
|
|
|
addMessageToChat(message, 'user');
|
|
|
|
|
|
|
|
|
const loadingElement = document.createElement('div');
|
|
|
loadingElement.className = 'loading';
|
|
|
loadingElement.id = 'loadingIndicator';
|
|
|
loadingElement.innerHTML = `
|
|
|
<div class="loading-dots">
|
|
|
<div class="loading-dot"></div>
|
|
|
<div class="loading-dot"></div>
|
|
|
<div class="loading-dot"></div>
|
|
|
</div>
|
|
|
<div class="loading-text">Thinking...</div>
|
|
|
`;
|
|
|
chatMessages.appendChild(loadingElement);
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
|
|
|
|
try {
|
|
|
const response = await fetch('/chatbot/chat', {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json',
|
|
|
},
|
|
|
body: JSON.stringify({ message: message })
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
document.getElementById('loadingIndicator').remove();
|
|
|
|
|
|
if (data.success) {
|
|
|
addMessageToChat(data.response, 'assistant');
|
|
|
} else {
|
|
|
addMessageToChat(`❌ ${data.response || 'Sorry, I encountered an error. Please try again.'}`, 'assistant');
|
|
|
}
|
|
|
} catch (error) {
|
|
|
|
|
|
document.getElementById('loadingIndicator').remove();
|
|
|
|
|
|
console.error('Error:', error);
|
|
|
addMessageToChat('❌ Sorry, I encountered an error. Please try again.', 'assistant');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function addMessageToChat(content, sender) {
|
|
|
|
|
|
const welcomeMessage = chatMessages.querySelector('.welcome-message');
|
|
|
if (welcomeMessage) {
|
|
|
welcomeMessage.remove();
|
|
|
}
|
|
|
|
|
|
const messageElement = document.createElement('div');
|
|
|
messageElement.className = `message ${sender}-message`;
|
|
|
|
|
|
|
|
|
let processedContent = content;
|
|
|
|
|
|
|
|
|
processedContent = processedContent.replace(/(https?:\/\/.*?\.(?:png|jpe?g|gif))/gi, '<img src="$1" class="chat-image" alt="Image from message">');
|
|
|
|
|
|
|
|
|
processedContent = processedContent.replace(/\$\$(.*?)\$\$/gs, '<div class="chat-code-block">$1</div>');
|
|
|
processedContent = processedContent.replace(/\\\[(.*?)\\\]/gs, '<div class="chat-code-block">$1</div>');
|
|
|
|
|
|
|
|
|
processedContent = processedContent.replace(/\$(.*?)\$/g, '<span class="chat-code-inline">$1</span>');
|
|
|
processedContent = processedContent.replace(/\\\((.*?)\\\)/g, '<span class="chat-code-inline">$1</span>');
|
|
|
|
|
|
|
|
|
processedContent = processedContent.replace(/```([\s\S]*?)```/g, '<div class="chat-code-block">$1</div>');
|
|
|
|
|
|
|
|
|
processedContent = processedContent.replace(/`([^`]+)`/g, '<span class="chat-code-inline">$1</span>');
|
|
|
|
|
|
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
|
|
|
messageElement.innerHTML = `
|
|
|
<div>${processedContent}</div>
|
|
|
<div class="message-info">${sender === 'user' ? 'You' : 'Assistant'} • ${time}</div>
|
|
|
`;
|
|
|
|
|
|
chatMessages.appendChild(messageElement);
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
|
|
|
|
|
|
|
sendButton.disabled = false;
|
|
|
}
|
|
|
|
|
|
|
|
|
function showNotification(message) {
|
|
|
const notification = document.getElementById("notification");
|
|
|
if (notification) {
|
|
|
notification.innerHTML = `<i class="fas fa-check-circle me-2"></i>${message}`;
|
|
|
notification.classList.add('show');
|
|
|
setTimeout(() => {
|
|
|
notification.classList.remove('show');
|
|
|
}, 3000);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latexInput.addEventListener('input', () => {
|
|
|
const text = latexInput.value;
|
|
|
charCount.textContent = `${text.length} characters`;
|
|
|
|
|
|
|
|
|
if (text.length > 500) {
|
|
|
charCount.classList.add('warning');
|
|
|
} else {
|
|
|
charCount.classList.remove('warning');
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
latexInput.addEventListener('keydown', (e) => {
|
|
|
if (e.ctrlKey && e.key === 'Enter') {
|
|
|
e.preventDefault();
|
|
|
convertLatexBtn.click();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
latexToTextToggle.addEventListener('click', () => {
|
|
|
latexToTextPanel.classList.toggle('active');
|
|
|
|
|
|
if (latexToTextPanel.classList.contains('active')) {
|
|
|
latexToTextToggle.style.display = 'none';
|
|
|
}
|
|
|
|
|
|
chatPanel.classList.remove('active');
|
|
|
});
|
|
|
|
|
|
|
|
|
latexToTextClose.addEventListener('click', () => {
|
|
|
latexToTextPanel.classList.remove('active');
|
|
|
|
|
|
latexToTextToggle.style.display = 'flex';
|
|
|
});
|
|
|
|
|
|
|
|
|
clearTextBtn.addEventListener('click', () => {
|
|
|
latexInput.value = '';
|
|
|
document.getElementById('math-output').innerHTML = 'Your rendered equation will appear here...';
|
|
|
convertedTextOutput.style.backgroundColor = 'white';
|
|
|
convertedTextOutput.style.borderColor = '#e9ecef';
|
|
|
charCount.textContent = '0 characters';
|
|
|
charCount.classList.remove('warning');
|
|
|
latexInput.focus();
|
|
|
});
|
|
|
|
|
|
|
|
|
convertedTextOutput.addEventListener('click', () => {
|
|
|
const selection = window.getSelection();
|
|
|
const range = document.createRange();
|
|
|
range.selectNodeContents(convertedTextOutput);
|
|
|
selection.removeAllRanges();
|
|
|
selection.addRange(range);
|
|
|
});
|
|
|
|
|
|
|
|
|
function convertLatexToText(latexStr) {
|
|
|
if (!latexStr) return "";
|
|
|
|
|
|
|
|
|
let text = latexStr;
|
|
|
|
|
|
|
|
|
text = text.replace(/\\begin\{.*?\}/g, '');
|
|
|
text = text.replace(/\\end\{.*?\}/g, '');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\frac\{([^}]*)\}\{([^}]*)\}/g, '($1)/($2)');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\sqrt\{([^}]*)\}/g, '√($1)');
|
|
|
text = text.replace(/\\sqrt/g, '√');
|
|
|
|
|
|
|
|
|
text = text.replace(/_\{([^}]*)\}/g, '_$1');
|
|
|
text = text.replace(/_(\w)/g, '_$1');
|
|
|
|
|
|
|
|
|
text = text.replace(/\^\{([^}]*)\}/g, '^$1');
|
|
|
text = text.replace(/\^(\w)/g, '^$1');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\int/g, '∫');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\sum/g, '∑');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\prod/g, '∏');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\lim/g, 'lim');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\infty/g, '∞');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\partial/g, '∂');
|
|
|
|
|
|
|
|
|
const greekLetters = {
|
|
|
'\\\\alpha': 'α',
|
|
|
'\\\\beta': 'β',
|
|
|
'\\\\gamma': 'γ',
|
|
|
'\\\\delta': 'δ',
|
|
|
'\\\\epsilon': 'ε',
|
|
|
'\\\\zeta': 'ζ',
|
|
|
'\\\\eta': 'η',
|
|
|
'\\\\theta': 'θ',
|
|
|
'\\\\iota': 'ι',
|
|
|
'\\\\kappa': 'κ',
|
|
|
'\\\\lambda': 'λ',
|
|
|
'\\\\mu': 'μ',
|
|
|
'\\\\nu': 'ν',
|
|
|
'\\\\xi': 'ξ',
|
|
|
'\\\\pi': 'π',
|
|
|
'\\\\rho': 'ρ',
|
|
|
'\\\\sigma': 'σ',
|
|
|
'\\\\tau': 'τ',
|
|
|
'\\\\upsilon': 'υ',
|
|
|
'\\\\phi': 'φ',
|
|
|
'\\\\chi': 'χ',
|
|
|
'\\\\psi': 'ψ',
|
|
|
'\\\\omega': 'ω',
|
|
|
'\\\\Gamma': 'Γ',
|
|
|
'\\\\Delta': 'Δ',
|
|
|
'\\\\Theta': 'Θ',
|
|
|
'\\\\Lambda': 'Λ',
|
|
|
'\\\\Xi': 'Ξ',
|
|
|
'\\\\Pi': 'Π',
|
|
|
'\\\\Sigma': 'Σ',
|
|
|
'\\\\Upsilon': 'Υ',
|
|
|
'\\\\Phi': 'Φ',
|
|
|
'\\\\Psi': 'Ψ',
|
|
|
'\\\\Omega': 'Ω'
|
|
|
};
|
|
|
|
|
|
for (const [pattern, replacement] of Object.entries(greekLetters)) {
|
|
|
const regex = new RegExp(pattern, 'g');
|
|
|
text = text.replace(regex, replacement);
|
|
|
}
|
|
|
|
|
|
|
|
|
const mathSymbols = {
|
|
|
'\\\\times': '×',
|
|
|
'\\\\cdot': '·',
|
|
|
'\\\\div': '÷',
|
|
|
'\\\\pm': '±',
|
|
|
'\\\\mp': '∓',
|
|
|
'\\\\leq': '≤',
|
|
|
'\\\\geq': '≥',
|
|
|
'\\\\neq': '≠',
|
|
|
'\\\\ne': '≠',
|
|
|
'\\\\approx': '≈',
|
|
|
'\\\\equiv': '≡',
|
|
|
'\\\\cong': '≅',
|
|
|
'\\\\sim': '∼',
|
|
|
'\\\\subset': '⊂',
|
|
|
'\\\\subseteq': '⊆',
|
|
|
'\\\\supset': '⊃',
|
|
|
'\\\\supseteq': '⊇',
|
|
|
'\\\\cup': '∪',
|
|
|
'\\\\cap': '∩',
|
|
|
'\\\\in': '∈',
|
|
|
'\\\\notin': '∉',
|
|
|
'\\\\forall': '∀',
|
|
|
'\\\\exists': '∃',
|
|
|
'\\\\nexists': '∄',
|
|
|
'\\\\emptyset': '∅',
|
|
|
'\\\\varnothing': '∅',
|
|
|
'\\\\to': '→',
|
|
|
'\\\\rightarrow': '→',
|
|
|
'\\\\Rightarrow': '⇒',
|
|
|
'\\\\leftrightarrow': '↔',
|
|
|
'\\\\Leftrightarrow': '⇔',
|
|
|
'\\\\mapsto': '↦',
|
|
|
'\\\\infty': '∞',
|
|
|
'\\\\angle': '∠',
|
|
|
'\\\\perp': '⊥',
|
|
|
'\\\\parallel': '∥',
|
|
|
'\\\\mid': '∣',
|
|
|
'\\\\nmid': '∤'
|
|
|
};
|
|
|
|
|
|
for (const [pattern, replacement] of Object.entries(mathSymbols)) {
|
|
|
const regex = new RegExp(pattern, 'g');
|
|
|
text = text.replace(regex, replacement);
|
|
|
}
|
|
|
|
|
|
|
|
|
text = text.replace(/\\mathbb\{R\}/g, 'ℝ');
|
|
|
text = text.replace(/\\mathbb\{N\}/g, 'ℕ');
|
|
|
text = text.replace(/\\mathbb\{Z\}/g, 'ℤ');
|
|
|
text = text.replace(/\\mathbb\{Q\}/g, 'ℚ');
|
|
|
text = text.replace(/\\mathbb\{C\}/g, 'ℂ');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\sin/g, 'sin');
|
|
|
text = text.replace(/\\cos/g, 'cos');
|
|
|
text = text.replace(/\\tan/g, 'tan');
|
|
|
text = text.replace(/\\cot/g, 'cot');
|
|
|
text = text.replace(/\\sec/g, 'sec');
|
|
|
text = text.replace(/\\csc/g, 'csc');
|
|
|
|
|
|
|
|
|
text = text.replace(/\\log/g, 'log');
|
|
|
text = text.replace(/\\ln/g, 'ln');
|
|
|
|
|
|
|
|
|
text = text.replace(/\{/g, '(').replace(/\}/g, ')');
|
|
|
text = text.replace(/\\\\/g, '');
|
|
|
|
|
|
|
|
|
text = text.replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
return text;
|
|
|
}
|
|
|
|
|
|
|
|
|
convertLatexBtn.addEventListener('click', () => {
|
|
|
const latexCode = latexInput.value.trim();
|
|
|
|
|
|
if (!latexCode) {
|
|
|
document.getElementById('math-output').innerHTML = 'Please enter some LaTeX code to render.';
|
|
|
convertedTextOutput.style.backgroundColor = '#fff3cd';
|
|
|
convertedTextOutput.style.borderColor = '#ffeaa7';
|
|
|
charCount.textContent = '0 characters';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const mathOutput = document.getElementById('math-output');
|
|
|
mathOutput.innerHTML = `\\(${latexCode}\\)`;
|
|
|
|
|
|
|
|
|
if (typeof MathJax !== 'undefined' && MathJax.typeset) {
|
|
|
MathJax.typesetPromise([mathOutput]).then(() => {
|
|
|
|
|
|
charCount.textContent = `${latexCode.length} characters`;
|
|
|
|
|
|
|
|
|
convertedTextOutput.style.backgroundColor = '#f8f9fa';
|
|
|
convertedTextOutput.style.borderColor = '#dee2e6';
|
|
|
});
|
|
|
} else {
|
|
|
|
|
|
mathOutput.innerHTML = `Rendered: ${latexCode}`;
|
|
|
charCount.textContent = `${latexCode.length} characters`;
|
|
|
convertedTextOutput.style.backgroundColor = '#f8f9fa';
|
|
|
convertedTextOutput.style.borderColor = '#dee2e6';
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('Rendering error:', error);
|
|
|
document.getElementById('math-output').innerHTML = 'An error occurred during rendering. Please check your LaTeX syntax.';
|
|
|
convertedTextOutput.style.backgroundColor = '#f8d7da';
|
|
|
convertedTextOutput.style.borderColor = '#f5c6cb';
|
|
|
charCount.textContent = '0 characters';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
copyTextBtn.addEventListener('click', () => {
|
|
|
const latexCode = latexInput.value.trim();
|
|
|
if (!latexCode) {
|
|
|
showNotification('Nothing to copy!');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
navigator.clipboard.writeText(latexCode).then(() => {
|
|
|
showNotification('LaTeX code copied to clipboard!');
|
|
|
}).catch(err => {
|
|
|
console.error('Failed to copy:', err);
|
|
|
showNotification('Failed to copy LaTeX code. Please try again.');
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
</script></body>
|
|
|
</html> |