Upload 2 files
Browse files- static/script.js +62 -83
- static/style.css +15 -14
static/script.js
CHANGED
|
@@ -2,9 +2,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 2 |
const chatContainer = document.getElementById("chat-container");
|
| 3 |
const chatForm = document.getElementById("chat-form");
|
| 4 |
const userInput = document.getElementById("user-input");
|
| 5 |
-
const sendBtn = document.getElementById("send-btn");
|
| 6 |
const usageStatsDiv = document.getElementById("usage-stats");
|
| 7 |
-
const modelStatsDiv = document.getElementById("model-stats");
|
| 8 |
const themeToggle = document.getElementById("theme-toggle");
|
| 9 |
const showSaveFormBtn = document.getElementById("show-save-form-btn");
|
| 10 |
const saveModalOverlay = document.getElementById("save-modal-overlay");
|
|
@@ -16,14 +14,28 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 16 |
const progressBar = document.getElementById("training-progress-bar");
|
| 17 |
const progressStep = document.getElementById("training-step");
|
| 18 |
const progressPercentage = document.getElementById("training-percentage");
|
| 19 |
-
const progressDetails = document.getElementById("training-details");
|
| 20 |
|
| 21 |
let eventSource = null;
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
function startTrainingListener() {
|
| 24 |
-
if (eventSource)
|
| 25 |
eventSource = new EventSource("/training-progress");
|
| 26 |
-
|
| 27 |
eventSource.onmessage = (event) => {
|
| 28 |
const data = JSON.parse(event.data);
|
| 29 |
if (data.is_training) {
|
|
@@ -31,56 +43,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 31 |
progressBar.style.width = `${data.percentage}%`;
|
| 32 |
progressStep.textContent = data.current_step;
|
| 33 |
progressPercentage.textContent = `${data.percentage}%`;
|
| 34 |
-
progressDetails.textContent = data.details;
|
| 35 |
} else {
|
| 36 |
trainingOverlay.classList.add("hidden");
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
eventSource.close();
|
| 40 |
-
eventSource = null;
|
| 41 |
-
}
|
| 42 |
}
|
| 43 |
};
|
| 44 |
-
|
| 45 |
-
eventSource.onerror = () => {
|
| 46 |
-
if (eventSource) eventSource.close();
|
| 47 |
-
eventSource = null;
|
| 48 |
-
// Retry after delay if needed
|
| 49 |
-
setTimeout(checkTrainingStatus, 3000);
|
| 50 |
-
};
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
async function checkTrainingStatus() {
|
| 54 |
-
const res = await fetch("/status");
|
| 55 |
-
const data = await res.json();
|
| 56 |
-
if (data.is_training) {
|
| 57 |
-
startTrainingListener();
|
| 58 |
-
} else {
|
| 59 |
-
trainingOverlay.classList.add("hidden");
|
| 60 |
-
}
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
async function updateStats() {
|
| 64 |
-
try {
|
| 65 |
-
const [statusRes, statsRes] = await Promise.all([fetch("/status"), fetch("/stats")]);
|
| 66 |
-
if (statusRes.ok) {
|
| 67 |
-
const s = await statusRes.json();
|
| 68 |
-
usageStatsDiv.innerHTML = `📊 ${s.tokens_used}/${s.max_tokens} رمز اليوم`;
|
| 69 |
-
if (s.is_training) startTrainingListener();
|
| 70 |
-
}
|
| 71 |
-
if (statsRes.ok) {
|
| 72 |
-
const s = await statsRes.json();
|
| 73 |
-
modelStatsDiv.innerHTML = `🧠 ${s.question_count} مدخلات في القاعدة`;
|
| 74 |
-
}
|
| 75 |
-
} catch (e) {}
|
| 76 |
}
|
| 77 |
|
| 78 |
function addMessage(text, className) {
|
| 79 |
const div = document.createElement("div");
|
| 80 |
div.className = `message ${className}`;
|
| 81 |
-
div.
|
| 82 |
chatContainer.appendChild(div);
|
| 83 |
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
|
|
| 84 |
}
|
| 85 |
|
| 86 |
chatForm.addEventListener("submit", async (e) => {
|
|
@@ -93,53 +70,59 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 93 |
|
| 94 |
const botMsgDiv = document.createElement("div");
|
| 95 |
botMsgDiv.className = "message bot-message";
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
| 97 |
chatContainer.appendChild(botMsgDiv);
|
| 98 |
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 99 |
|
| 100 |
try {
|
| 101 |
-
const
|
| 102 |
method: "POST",
|
| 103 |
headers: { "Content-Type": "application/json" },
|
| 104 |
body: JSON.stringify({ question: q })
|
| 105 |
});
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
if (
|
| 111 |
-
} else if (data.ambiguous_questions) {
|
| 112 |
-
botMsgDiv.textContent = "أي من هذه الأسئلة تقصد؟";
|
| 113 |
-
const btnContainer = document.createElement("div");
|
| 114 |
-
btnContainer.style.display = "flex";
|
| 115 |
-
btnContainer.style.flexDirection = "column";
|
| 116 |
-
btnContainer.style.gap = "5px";
|
| 117 |
-
btnContainer.style.marginTop = "10px";
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
} catch (err) {
|
| 140 |
botMsgDiv.textContent = "❌ حدث خطأ في الاتصال.";
|
| 141 |
}
|
| 142 |
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 143 |
updateStats();
|
| 144 |
});
|
| 145 |
|
|
@@ -158,11 +141,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 158 |
e.preventDefault();
|
| 159 |
const question = document.getElementById("new-question").value.trim();
|
| 160 |
const answers = Array.from(document.querySelectorAll(".answer-input")).map(i => i.value.trim()).filter(Boolean);
|
| 161 |
-
|
| 162 |
if (!question || !answers.length) return;
|
| 163 |
-
|
| 164 |
const data = answers.map(a => ({ question, answer: a }));
|
| 165 |
-
|
| 166 |
try {
|
| 167 |
const res = await fetch("/save", {
|
| 168 |
method: "POST",
|
|
@@ -172,7 +152,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 172 |
if (res.ok) {
|
| 173 |
saveModalOverlay.classList.add("hidden");
|
| 174 |
saveForm.reset();
|
| 175 |
-
|
| 176 |
}
|
| 177 |
} catch (err) {}
|
| 178 |
};
|
|
@@ -182,6 +162,5 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 182 |
themeToggle.textContent = isDark ? "☀️" : "🌙";
|
| 183 |
};
|
| 184 |
|
| 185 |
-
checkTrainingStatus();
|
| 186 |
updateStats();
|
| 187 |
});
|
|
|
|
| 2 |
const chatContainer = document.getElementById("chat-container");
|
| 3 |
const chatForm = document.getElementById("chat-form");
|
| 4 |
const userInput = document.getElementById("user-input");
|
|
|
|
| 5 |
const usageStatsDiv = document.getElementById("usage-stats");
|
|
|
|
| 6 |
const themeToggle = document.getElementById("theme-toggle");
|
| 7 |
const showSaveFormBtn = document.getElementById("show-save-form-btn");
|
| 8 |
const saveModalOverlay = document.getElementById("save-modal-overlay");
|
|
|
|
| 14 |
const progressBar = document.getElementById("training-progress-bar");
|
| 15 |
const progressStep = document.getElementById("training-step");
|
| 16 |
const progressPercentage = document.getElementById("training-percentage");
|
|
|
|
| 17 |
|
| 18 |
let eventSource = null;
|
| 19 |
|
| 20 |
+
async function updateStats() {
|
| 21 |
+
try {
|
| 22 |
+
const res = await fetch("/status");
|
| 23 |
+
const s = await res.json();
|
| 24 |
+
usageStatsDiv.innerHTML = `
|
| 25 |
+
<div class="sys-stats">
|
| 26 |
+
<span>📊 ${s.tokens_used}/${s.max_tokens} Token</span>
|
| 27 |
+
<span>💻 CPU: ${s.cpu}%</span>
|
| 28 |
+
<span>🧠 RAM: ${s.ram}%</span>
|
| 29 |
+
</div>
|
| 30 |
+
`;
|
| 31 |
+
if (s.is_training) startTrainingListener();
|
| 32 |
+
} catch (e) {}
|
| 33 |
+
}
|
| 34 |
+
setInterval(updateStats, 3000);
|
| 35 |
+
|
| 36 |
function startTrainingListener() {
|
| 37 |
+
if (eventSource) return;
|
| 38 |
eventSource = new EventSource("/training-progress");
|
|
|
|
| 39 |
eventSource.onmessage = (event) => {
|
| 40 |
const data = JSON.parse(event.data);
|
| 41 |
if (data.is_training) {
|
|
|
|
| 43 |
progressBar.style.width = `${data.percentage}%`;
|
| 44 |
progressStep.textContent = data.current_step;
|
| 45 |
progressPercentage.textContent = `${data.percentage}%`;
|
|
|
|
| 46 |
} else {
|
| 47 |
trainingOverlay.classList.add("hidden");
|
| 48 |
+
eventSource.close();
|
| 49 |
+
eventSource = null;
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
function addMessage(text, className) {
|
| 55 |
const div = document.createElement("div");
|
| 56 |
div.className = `message ${className}`;
|
| 57 |
+
div.innerHTML = `<div class="msg-content">${text}</div>`;
|
| 58 |
chatContainer.appendChild(div);
|
| 59 |
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 60 |
+
return div;
|
| 61 |
}
|
| 62 |
|
| 63 |
chatForm.addEventListener("submit", async (e) => {
|
|
|
|
| 70 |
|
| 71 |
const botMsgDiv = document.createElement("div");
|
| 72 |
botMsgDiv.className = "message bot-message";
|
| 73 |
+
const statusEl = document.createElement("div");
|
| 74 |
+
statusEl.className = "generating-status";
|
| 75 |
+
statusEl.textContent = "جاري البدء ... 0%";
|
| 76 |
+
botMsgDiv.appendChild(statusEl);
|
| 77 |
chatContainer.appendChild(botMsgDiv);
|
|
|
|
| 78 |
|
| 79 |
try {
|
| 80 |
+
const response = await fetch("/chat", {
|
| 81 |
method: "POST",
|
| 82 |
headers: { "Content-Type": "application/json" },
|
| 83 |
body: JSON.stringify({ question: q })
|
| 84 |
});
|
| 85 |
+
|
| 86 |
+
if (!response.ok) {
|
| 87 |
+
const errData = await response.json();
|
| 88 |
+
botMsgDiv.textContent = `❌ ${errData.error || "Error"}`;
|
| 89 |
+
return;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const reader = response.body.getReader();
|
| 93 |
+
const decoder = new TextDecoder();
|
| 94 |
|
| 95 |
+
while (true) {
|
| 96 |
+
const { value, done } = await reader.read();
|
| 97 |
+
if (done) break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
const chunk = decoder.decode(value);
|
| 100 |
+
const lines = chunk.split("\n");
|
| 101 |
+
|
| 102 |
+
for (const line of lines) {
|
| 103 |
+
if (!line.trim()) continue;
|
| 104 |
+
try {
|
| 105 |
+
const data = JSON.parse(line);
|
| 106 |
+
|
| 107 |
+
if (data.status === "Generating") {
|
| 108 |
+
statusEl.textContent = `${data.step} ... ${data.percentage}%`;
|
| 109 |
+
} else if (data.status === "Complete" || data.response_text) {
|
| 110 |
+
botMsgDiv.innerHTML = `
|
| 111 |
+
<div class="msg-content">${data.response_text}</div>
|
| 112 |
+
<div class="msg-footer">
|
| 113 |
+
⏱️ ${data.duration.toFixed(3)}s | 🔤 ${data.token_count} Token | 💻 ${data.cpu}% CPU
|
| 114 |
+
</div>
|
| 115 |
+
`;
|
| 116 |
+
} else if (data.error) {
|
| 117 |
+
botMsgDiv.textContent = `❌ ${data.error}`;
|
| 118 |
+
}
|
| 119 |
+
} catch (e) { console.error("Error parsing chunk", e); }
|
| 120 |
+
}
|
| 121 |
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 122 |
}
|
| 123 |
} catch (err) {
|
| 124 |
botMsgDiv.textContent = "❌ حدث خطأ في الاتصال.";
|
| 125 |
}
|
|
|
|
| 126 |
updateStats();
|
| 127 |
});
|
| 128 |
|
|
|
|
| 141 |
e.preventDefault();
|
| 142 |
const question = document.getElementById("new-question").value.trim();
|
| 143 |
const answers = Array.from(document.querySelectorAll(".answer-input")).map(i => i.value.trim()).filter(Boolean);
|
|
|
|
| 144 |
if (!question || !answers.length) return;
|
|
|
|
| 145 |
const data = answers.map(a => ({ question, answer: a }));
|
|
|
|
| 146 |
try {
|
| 147 |
const res = await fetch("/save", {
|
| 148 |
method: "POST",
|
|
|
|
| 152 |
if (res.ok) {
|
| 153 |
saveModalOverlay.classList.add("hidden");
|
| 154 |
saveForm.reset();
|
| 155 |
+
updateStats();
|
| 156 |
}
|
| 157 |
} catch (err) {}
|
| 158 |
};
|
|
|
|
| 162 |
themeToggle.textContent = isDark ? "☀️" : "🌙";
|
| 163 |
};
|
| 164 |
|
|
|
|
| 165 |
updateStats();
|
| 166 |
});
|
static/style.css
CHANGED
|
@@ -26,19 +26,24 @@ body { background: var(--bg); color: var(--text); direction: rtl; height: 100vh;
|
|
| 26 |
|
| 27 |
#app-container { width: 100%; max-width: 500px; height: 95vh; background: var(--card); border-radius: 20px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.1); border: 1px solid var(--border); }
|
| 28 |
|
| 29 |
-
header { padding: 20px; background: var(--card); border-bottom: 1px solid var(--border); }
|
| 30 |
.header-main { display: flex; justify-content: space-between; align-items: center; }
|
| 31 |
-
h1 { font-size: 1.
|
| 32 |
-
.header-actions { display: flex; gap:
|
| 33 |
-
.icon-btn { background: var(--bot-msg); border: none; width:
|
| 34 |
|
| 35 |
-
#usage-stats {
|
|
|
|
|
|
|
| 36 |
|
| 37 |
#chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; }
|
| 38 |
-
.message { max-width: 85%; padding: 12px 16px; border-radius: 18px; font-size: 0.95rem;
|
| 39 |
.bot-message { background: var(--bot-msg); align-self: flex-start; border-bottom-right-radius: 4px; }
|
| 40 |
.user-message { background: var(--user-msg); color: var(--user-text); align-self: flex-end; border-bottom-left-radius: 4px; }
|
| 41 |
|
|
|
|
|
|
|
|
|
|
| 42 |
#chat-form-container { padding: 15px; background: var(--card); border-top: 1px solid var(--border); }
|
| 43 |
#chat-form { display: flex; gap: 10px; }
|
| 44 |
#user-input { flex: 1; padding: 12px 20px; border-radius: 25px; border: 1px solid var(--border); background: var(--bg); color: var(--text); outline: none; }
|
|
@@ -47,14 +52,12 @@ h1 { font-size: 1.2rem; font-weight: 700; color: var(--primary); }
|
|
| 47 |
/* Training Overlay */
|
| 48 |
#training-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(8px); z-index: 9999; display: flex; justify-content: center; align-items: center; color: white; }
|
| 49 |
.training-card { background: #1c1c1e; padding: 40px; border-radius: 24px; text-align: center; width: 90%; max-width: 400px; border: 1px solid #333; }
|
| 50 |
-
.loader { width:
|
| 51 |
@keyframes spin { 100% { transform: rotate(360deg); } }
|
| 52 |
|
| 53 |
-
.progress-container { background: #333; height:
|
| 54 |
-
.progress-bar { background: var(--primary); height: 100%; width: 0%; transition: width 0.3s ease;
|
| 55 |
-
#training-details { font-size: 0.85rem; opacity: 0.6; margin-top: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 56 |
|
| 57 |
-
/* Modal */
|
| 58 |
#save-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; z-index: 1000; }
|
| 59 |
#save-modal { background: var(--card); padding: 25px; border-radius: 20px; width: 90%; max-width: 450px; }
|
| 60 |
#save-form { display: flex; flex-direction: column; gap: 15px; }
|
|
@@ -62,9 +65,7 @@ h1 { font-size: 1.2rem; font-weight: 700; color: var(--primary); }
|
|
| 62 |
.modal-actions { display: flex; gap: 10px; }
|
| 63 |
.modal-actions button { flex: 1; padding: 12px; border-radius: 10px; border: none; cursor: pointer; font-weight: 700; }
|
| 64 |
#cancel-save-btn { background: var(--bot-msg); color: var(--text); }
|
| 65 |
-
button[type="submit"] { background: var(--primary); color: white; }
|
| 66 |
|
| 67 |
.hidden { display: none !important; }
|
| 68 |
-
|
| 69 |
-
::-webkit-scrollbar { width: 6px; }
|
| 70 |
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
|
|
|
|
| 26 |
|
| 27 |
#app-container { width: 100%; max-width: 500px; height: 95vh; background: var(--card); border-radius: 20px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.1); border: 1px solid var(--border); }
|
| 28 |
|
| 29 |
+
header { padding: 15px 20px; background: var(--card); border-bottom: 1px solid var(--border); }
|
| 30 |
.header-main { display: flex; justify-content: space-between; align-items: center; }
|
| 31 |
+
h1 { font-size: 1.1rem; font-weight: 700; color: var(--primary); }
|
| 32 |
+
.header-actions { display: flex; gap: 8px; }
|
| 33 |
+
.icon-btn { background: var(--bot-msg); border: none; width: 35px; height: 35px; border-radius: 10px; cursor: pointer; color: var(--text); font-size: 1rem; }
|
| 34 |
|
| 35 |
+
#usage-stats { margin-top: 10px; }
|
| 36 |
+
.sys-stats { display: flex; gap: 10px; flex-wrap: wrap; font-size: 0.75rem; opacity: 0.8; }
|
| 37 |
+
.sys-stats span { background: var(--bot-msg); padding: 4px 8px; border-radius: 6px; border: 1px solid var(--border); }
|
| 38 |
|
| 39 |
#chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; }
|
| 40 |
+
.message { max-width: 85%; padding: 12px 16px; border-radius: 18px; font-size: 0.95rem; position: relative; }
|
| 41 |
.bot-message { background: var(--bot-msg); align-self: flex-start; border-bottom-right-radius: 4px; }
|
| 42 |
.user-message { background: var(--user-msg); color: var(--user-text); align-self: flex-end; border-bottom-left-radius: 4px; }
|
| 43 |
|
| 44 |
+
.msg-footer { font-size: 0.65rem; opacity: 0.6; margin-top: 8px; border-top: 1px solid rgba(128,128,128,0.2); padding-top: 4px; }
|
| 45 |
+
.generating-status { font-style: italic; font-size: 0.85rem; opacity: 0.7; color: var(--primary); }
|
| 46 |
+
|
| 47 |
#chat-form-container { padding: 15px; background: var(--card); border-top: 1px solid var(--border); }
|
| 48 |
#chat-form { display: flex; gap: 10px; }
|
| 49 |
#user-input { flex: 1; padding: 12px 20px; border-radius: 25px; border: 1px solid var(--border); background: var(--bg); color: var(--text); outline: none; }
|
|
|
|
| 52 |
/* Training Overlay */
|
| 53 |
#training-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(8px); z-index: 9999; display: flex; justify-content: center; align-items: center; color: white; }
|
| 54 |
.training-card { background: #1c1c1e; padding: 40px; border-radius: 24px; text-align: center; width: 90%; max-width: 400px; border: 1px solid #333; }
|
| 55 |
+
.loader { width: 50px; height: 50px; border: 4px solid #333; border-top: 4px solid var(--primary); border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 20px; }
|
| 56 |
@keyframes spin { 100% { transform: rotate(360deg); } }
|
| 57 |
|
| 58 |
+
.progress-container { background: #333; height: 8px; border-radius: 4px; margin: 15px 0; overflow: hidden; }
|
| 59 |
+
.progress-bar { background: var(--primary); height: 100%; width: 0%; transition: width 0.3s ease; }
|
|
|
|
| 60 |
|
|
|
|
| 61 |
#save-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; z-index: 1000; }
|
| 62 |
#save-modal { background: var(--card); padding: 25px; border-radius: 20px; width: 90%; max-width: 450px; }
|
| 63 |
#save-form { display: flex; flex-direction: column; gap: 15px; }
|
|
|
|
| 65 |
.modal-actions { display: flex; gap: 10px; }
|
| 66 |
.modal-actions button { flex: 1; padding: 12px; border-radius: 10px; border: none; cursor: pointer; font-weight: 700; }
|
| 67 |
#cancel-save-btn { background: var(--bot-msg); color: var(--text); }
|
|
|
|
| 68 |
|
| 69 |
.hidden { display: none !important; }
|
| 70 |
+
::-webkit-scrollbar { width: 5px; }
|
|
|
|
| 71 |
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
|