gebsunamy commited on
Commit
2cbc36c
·
verified ·
1 Parent(s): 932c395

Upload 2 files

Browse files
Files changed (2) hide show
  1. static/script.js +166 -123
  2. static/style.css +292 -66
static/script.js CHANGED
@@ -1,168 +1,211 @@
1
  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 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");
9
- const saveForm = document.getElementById("save-form");
10
- const cancelSaveBtn = document.getElementById("cancel-save-btn");
11
- const addAnswerBtn = document.getElementById("add-answer-btn");
12
- const answersContainer = document.getElementById("answers-container");
13
- const trainingOverlay = document.getElementById("training-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) {
42
- trainingOverlay.classList.remove("hidden");
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) => {
64
  e.preventDefault();
65
  const q = userInput.value.trim();
66
  if (!q) return;
67
 
68
- addMessage(q, "user-message");
69
  userInput.value = "";
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
- // Immediately update stats display after completion
117
- updateStats();
118
- } else if (data.error) {
119
- botMsgDiv.textContent = `❌ ${data.error}`;
120
- }
121
- } catch (e) { console.error("Error parsing chunk", e); }
122
  }
123
- chatContainer.scrollTop = chatContainer.scrollHeight;
124
  }
125
  } catch (err) {
126
- botMsgDiv.textContent = "❌ حدث خطأ في الاتصال.";
127
  }
128
- updateStats();
129
- });
130
-
131
- showSaveFormBtn.onclick = () => saveModalOverlay.classList.remove("hidden");
132
- cancelSaveBtn.onclick = () => saveModalOverlay.classList.add("hidden");
133
-
134
- addAnswerBtn.onclick = () => {
135
- const input = document.createElement("input");
136
- input.type = "text";
137
- input.className = "answer-input";
138
- input.placeholder = "إجابة أخرى...";
139
- answersContainer.appendChild(input);
140
  };
141
 
142
- saveForm.onsubmit = async (e) => {
143
- e.preventDefault();
144
- const question = document.getElementById("new-question").value.trim();
145
- const answers = Array.from(document.querySelectorAll(".answer-input")).map(i => i.value.trim()).filter(Boolean);
146
- if (!question || !answers.length) return;
147
- const data = answers.map(a => ({ question, answer: a }));
148
  try {
149
- const res = await fetch("/save", {
150
- method: "POST",
151
- headers: { "Content-Type": "application/json" },
152
- body: JSON.stringify(data)
153
- });
154
- if (res.ok) {
155
- saveModalOverlay.classList.add("hidden");
156
- saveForm.reset();
157
- updateStats();
 
 
 
 
 
 
 
 
 
158
  }
159
- } catch (err) {}
160
- };
161
 
162
- themeToggle.onclick = () => {
163
- const isDark = document.body.classList.toggle("dark-mode");
164
- themeToggle.textContent = isDark ? "☀️" : "🌙";
165
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- updateStats();
168
  });
 
1
  document.addEventListener("DOMContentLoaded", () => {
2
+ // --- UI Elements ---
3
+ const sidebar = document.getElementById("sidebar");
4
+ const openSidebarBtn = document.getElementById("open-sidebar");
5
+ const closeSidebarBtn = document.getElementById("close-sidebar");
6
+ const chatMessages = document.getElementById("chat-messages");
7
  const chatForm = document.getElementById("chat-form");
8
  const userInput = document.getElementById("user-input");
9
+ const usageDisplay = document.getElementById("usage-display");
10
+ const authTrigger = document.getElementById("auth-trigger");
11
+ const authModal = document.getElementById("auth-modal");
12
+ const closeModalBtn = document.querySelector(".close-modal");
13
+ const loginForm = document.getElementById("login-form");
14
+ const signupForm = document.getElementById("signup-form");
15
+ const logoutBtn = document.getElementById("logout-btn");
16
+ const tabBtns = document.querySelectorAll(".tab-btn");
17
+ const historyList = document.getElementById("history-list");
18
+
19
+ // --- State ---
20
+ let isLoggedIn = false;
21
+
22
+ // --- Sidebar Logic ---
23
+ openSidebarBtn.onclick = () => {
24
+ sidebar.classList.remove("sidebar-closed");
25
+ if (isLoggedIn) fetchHistory();
26
+ };
27
+ closeSidebarBtn.onclick = () => sidebar.classList.add("sidebar-closed");
28
+
29
+ // --- Auth Logic ---
30
+ authTrigger.onclick = () => authModal.classList.remove("hidden");
31
+ closeModalBtn.onclick = () => authModal.classList.add("hidden");
32
+
33
+ tabBtns.forEach(btn => {
34
+ btn.onclick = () => {
35
+ tabBtns.forEach(b => b.classList.remove("active"));
36
+ btn.classList.add("active");
37
+ if (btn.dataset.tab === "login") {
38
+ loginForm.classList.remove("hidden");
39
+ signupForm.classList.add("hidden");
 
 
 
 
 
 
 
 
 
 
40
  } else {
41
+ loginForm.classList.add("hidden");
42
+ signupForm.classList.remove("hidden");
 
43
  }
44
  };
45
+ });
46
+
47
+ loginForm.onsubmit = async (e) => {
48
+ e.preventDefault();
49
+ const [email, password] = loginForm.querySelectorAll("input");
50
+ const res = await fetch("/login", {
51
+ method: "POST",
52
+ headers: {"Content-Type": "application/json"},
53
+ body: JSON.stringify({email: email.value, password: password.value})
54
+ });
55
+ const data = await res.json();
56
+ if (data.success) {
57
+ authModal.classList.add("hidden");
58
+ location.reload();
59
+ } else {
60
+ alert(data.error);
61
+ }
62
+ };
63
+
64
+ signupForm.onsubmit = async (e) => {
65
+ e.preventDefault();
66
+ const [email, password] = signupForm.querySelectorAll("input");
67
+ const res = await fetch("/signup", {
68
+ method: "POST",
69
+ headers: {"Content-Type": "application/json"},
70
+ body: JSON.stringify({email: email.value, password: password.value})
71
+ });
72
+ const data = await res.json();
73
+ if (data.success) {
74
+ alert("Account created! Please login.");
75
+ tabBtns[0].click();
76
+ } else {
77
+ alert(data.error);
78
+ }
79
+ };
80
+
81
+ logoutBtn.onclick = async () => {
82
+ await fetch("/logout");
83
+ location.reload();
84
+ };
85
 
86
+ // --- Chat Logic ---
87
+ function addMessage(text, type, stats = null) {
88
  const div = document.createElement("div");
89
+ div.className = `message ${type === 'user' ? 'user-msg' : 'bot-msg'} fade-in`;
90
+
91
+ let footerHtml = stats ? `
92
+ <div class="msg-footer">
93
+ <span><i class="fas fa-clock"></i> ${stats.duration.toFixed(3)}s</span>
94
+ <span><i class="fas fa-font"></i> ${stats.token_count} Token</span>
95
+ </div>
96
+ ` : '';
97
+
98
+ div.innerHTML = `
99
+ <div class="msg-bubble">${text}</div>
100
+ ${footerHtml}
101
+ `;
102
+ chatMessages.appendChild(div);
103
+ document.getElementById("chat-viewport").scrollTop = chatMessages.scrollHeight;
104
  return div;
105
  }
106
 
107
+ chatForm.onsubmit = async (e) => {
108
  e.preventDefault();
109
  const q = userInput.value.trim();
110
  if (!q) return;
111
 
112
+ addMessage(q, 'user');
113
  userInput.value = "";
114
+
115
  const botMsgDiv = document.createElement("div");
116
+ botMsgDiv.className = "message bot-msg fade-in";
117
  const statusEl = document.createElement("div");
118
  statusEl.className = "generating-status";
119
+ statusEl.textContent = "جاري البدء...";
120
  botMsgDiv.appendChild(statusEl);
121
+ chatMessages.appendChild(botMsgDiv);
122
 
123
  try {
124
+ const res = await fetch("/chat", {
125
  method: "POST",
126
+ headers: {"Content-Type": "application/json"},
127
+ body: JSON.stringify({question: q})
128
  });
129
 
130
+ if (!res.ok) {
131
+ const data = await res.json();
132
+ botMsgDiv.innerHTML = `<div class="msg-bubble">❌ ${data.error}</div>`;
133
  return;
134
  }
135
 
136
+ const reader = res.body.getReader();
137
  const decoder = new TextDecoder();
138
+
139
  while (true) {
140
+ const {value, done} = await reader.read();
141
  if (done) break;
142
 
143
+ const lines = decoder.decode(value).split("\n");
 
 
144
  for (const line of lines) {
145
  if (!line.trim()) continue;
146
+ const data = JSON.parse(line);
147
+ if (data.status === "Generating") {
148
+ statusEl.textContent = `${data.step} ... ${data.percentage}%`;
149
+ } else if (data.status === "Complete") {
150
+ botMsgDiv.innerHTML = `
151
+ <div class="msg-bubble">${data.response_text}</div>
152
+ <div class="msg-footer">
153
+ <span><i class="fas fa-clock"></i> ${data.duration.toFixed(3)}s</span>
154
+ <span><i class="fas fa-font"></i> ${data.token_count} Token</span>
155
+ </div>
156
+ `;
157
+ updateStatus();
158
+ }
 
 
 
 
 
159
  }
 
160
  }
161
  } catch (err) {
162
+ botMsgDiv.innerHTML = `<div class="msg-bubble">❌ حدث خطأ في الاتصال</div>`;
163
  }
 
 
 
 
 
 
 
 
 
 
 
 
164
  };
165
 
166
+ // --- Helper Functions ---
167
+ async function updateStatus() {
 
 
 
 
168
  try {
169
+ const res = await fetch("/status");
170
+ const data = await res.json();
171
+ isLoggedIn = data.logged_in;
172
+
173
+ if (isLoggedIn) {
174
+ authTrigger.classList.add("hidden");
175
+ logoutBtn.classList.remove("hidden");
176
+ usageDisplay.innerHTML = `
177
+ <span><i class="fas fa-user-circle"></i> ${data.user}</span>
178
+ <span><i class="fas fa-coins"></i> ${data.tokens}/${data.max}</span>
179
+ `;
180
+ } else {
181
+ authTrigger.classList.remove("hidden");
182
+ logoutBtn.classList.add("hidden");
183
+ usageDisplay.innerHTML = `
184
+ <span><i class="fas fa-ghost"></i> ضيف</span>
185
+ <span><i class="fas fa-coins"></i> ${data.tokens}/${data.max}</span>
186
+ `;
187
  }
188
+ } catch (e) {}
189
+ }
190
 
191
+ async function fetchHistory() {
192
+ const res = await fetch("/history");
193
+ const data = await res.json();
194
+ historyList.innerHTML = "";
195
+ data.reverse().forEach(item => {
196
+ const div = document.createElement("div");
197
+ div.className = "history-item";
198
+ div.textContent = item.q;
199
+ div.title = item.q;
200
+ div.onclick = () => {
201
+ chatMessages.innerHTML = "";
202
+ addMessage(item.q, 'user');
203
+ addMessage(item.a, 'bot');
204
+ sidebar.classList.add("sidebar-closed");
205
+ };
206
+ historyList.appendChild(div);
207
+ });
208
+ }
209
 
210
+ updateStatus();
211
  });
static/style.css CHANGED
@@ -1,71 +1,297 @@
1
  @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap');
2
 
3
  :root {
4
- --primary: #0084ff;
5
- --primary-glow: rgba(0, 132, 255, 0.5);
6
- --bg: #f8f9fa;
7
- --card: #ffffff;
8
- --text: #1a1a1a;
9
- --bot-msg: #f0f2f5;
10
- --user-msg: #0084ff;
11
- --user-text: #ffffff;
12
- --border: #e4e6eb;
13
- }
14
-
15
- body.dark-mode {
16
- --bg: #0b0e11;
17
- --card: #15191d;
18
- --text: #e4e6eb;
19
- --bot-msg: #242526;
20
- --border: #303338;
21
- }
22
-
23
- * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Tajawal', sans-serif; }
24
-
25
- body { background: var(--bg); color: var(--text); direction: rtl; height: 100vh; display: flex; justify-content: center; align-items: center; }
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; }
50
- #send-btn { background: var(--primary); color: white; border: none; width: 45px; height: 45px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; transform: rotate(180deg); }
51
-
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; }
64
- #save-form input { padding: 12px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg); color: var(--text); }
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; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap');
2
 
3
  :root {
4
+ --primary: #6366f1;
5
+ --primary-glow: rgba(99, 102, 241, 0.4);
6
+ --bg-dark: #0f172a;
7
+ --glass-bg: rgba(30, 41, 59, 0.7);
8
+ --glass-border: rgba(255, 255, 255, 0.1);
9
+ --text-main: #f1f5f9;
10
+ --text-muted: #94a3b8;
11
+ --bot-msg: rgba(51, 65, 85, 0.5);
12
+ --user-msg: #6366f1;
13
+ }
14
+
15
+ * {
16
+ margin: 0;
17
+ padding: 0;
18
+ box-sizing: border-box;
19
+ font-family: 'Tajawal', sans-serif;
20
+ -webkit-tap-highlight-color: transparent;
21
+ }
22
+
23
+ body {
24
+ background: var(--bg-dark);
25
+ color: var(--text-main);
26
+ direction: rtl;
27
+ height: 100vh;
28
+ overflow: hidden;
29
+ }
30
+
31
+ #app-wrapper {
32
+ display: flex;
33
+ height: 100vh;
34
+ }
35
+
36
+ /* --- Glassmorphism Elements --- */
37
+ .glass-card {
38
+ background: var(--glass-bg);
39
+ backdrop-filter: blur(12px);
40
+ -webkit-backdrop-filter: blur(12px);
41
+ border: 1px solid var(--glass-border);
42
+ border-radius: 20px;
43
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
44
+ }
45
+
46
+ .glass-header {
47
+ height: 70px;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ padding: 0 20px;
52
+ background: rgba(15, 23, 42, 0.8);
53
+ backdrop-filter: blur(8px);
54
+ border-bottom: 1px solid var(--glass-border);
55
+ z-index: 100;
56
+ }
57
+
58
+ /* --- Sidebar --- */
59
+ #sidebar {
60
+ width: 280px;
61
+ height: 100%;
62
+ background: #1e293b;
63
+ border-left: 1px solid var(--glass-border);
64
+ display: flex;
65
+ flex-direction: column;
66
+ transition: transform 0.3s ease;
67
+ position: fixed;
68
+ right: 0;
69
+ z-index: 200;
70
+ }
71
+
72
+ #sidebar.sidebar-closed {
73
+ transform: translateX(100%);
74
+ }
75
+
76
+ .sidebar-header {
77
+ padding: 20px;
78
+ display: flex;
79
+ justify-content: space-between;
80
+ align-items: center;
81
+ border-bottom: 1px solid var(--glass-border);
82
+ }
83
+
84
+ #history-list {
85
+ flex: 1;
86
+ overflow-y: auto;
87
+ padding: 10px;
88
+ }
89
+
90
+ .history-item {
91
+ padding: 12px;
92
+ margin-bottom: 8px;
93
+ border-radius: 12px;
94
+ background: rgba(255, 255, 255, 0.05);
95
+ cursor: pointer;
96
+ transition: background 0.2s;
97
+ font-size: 0.9rem;
98
+ white-space: nowrap;
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ }
102
+
103
+ .history-item:hover {
104
+ background: rgba(255, 255, 255, 0.1);
105
+ }
106
+
107
+ /* --- Main Viewport --- */
108
+ #main-content {
109
+ flex: 1;
110
+ display: flex;
111
+ flex-direction: column;
112
+ position: relative;
113
+ width: 100%;
114
+ }
115
+
116
+ #chat-viewport {
117
+ flex: 1;
118
+ overflow-y: auto;
119
+ padding: 20px;
120
+ display: flex;
121
+ flex-direction: column;
122
+ scroll-behavior: smooth;
123
+ }
124
+
125
+ #chat-messages {
126
+ max-width: 800px;
127
+ width: 100%;
128
+ margin: 0 auto;
129
+ display: flex;
130
+ flex-direction: column;
131
+ gap: 16px;
132
+ }
133
+
134
+ .message {
135
+ max-width: 85%;
136
+ display: flex;
137
+ flex-direction: column;
138
+ }
139
+
140
+ .bot-msg { align-self: flex-start; }
141
+ .user-msg { align-self: flex-end; }
142
+
143
+ .msg-bubble {
144
+ padding: 14px 18px;
145
+ border-radius: 18px;
146
+ line-height: 1.6;
147
+ font-size: 1rem;
148
+ position: relative;
149
+ animation: fadeIn 0.3s ease;
150
+ }
151
+
152
+ .bot-msg .msg-bubble {
153
+ background: var(--bot-msg);
154
+ border: 1px solid var(--glass-border);
155
+ border-bottom-right-radius: 4px;
156
+ }
157
+
158
+ .user-msg .msg-bubble {
159
+ background: var(--user-msg);
160
+ color: white;
161
+ border-bottom-left-radius: 4px;
162
+ box-shadow: 0 4px 12px var(--primary-glow);
163
+ }
164
+
165
+ .msg-footer {
166
+ font-size: 0.7rem;
167
+ color: var(--text-muted);
168
+ margin-top: 6px;
169
+ display: flex;
170
+ gap: 10px;
171
+ }
172
+
173
+ /* --- Input Area --- */
174
+ .input-area {
175
+ padding: 20px;
176
+ background: linear-gradient(to top, var(--bg-dark), transparent);
177
+ }
178
+
179
+ .glass-form {
180
+ max-width: 800px;
181
+ margin: 0 auto;
182
+ display: flex;
183
+ background: var(--glass-bg);
184
+ backdrop-filter: blur(10px);
185
+ border: 1px solid var(--glass-border);
186
+ border-radius: 30px;
187
+ padding: 6px;
188
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
189
+ }
190
+
191
+ #user-input {
192
+ flex: 1;
193
+ background: transparent;
194
+ border: none;
195
+ color: white;
196
+ padding: 10px 20px;
197
+ outline: none;
198
+ font-size: 1rem;
199
+ }
200
+
201
+ #send-btn {
202
+ width: 45px;
203
+ height: 45px;
204
+ background: var(--primary);
205
+ border: none;
206
+ border-radius: 50%;
207
+ color: white;
208
+ cursor: pointer;
209
+ transition: transform 0.2s;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ }
214
+
215
+ #send-btn:hover { transform: scale(1.05); }
216
+
217
+ /* --- Modals --- */
218
+ .modal {
219
+ position: fixed;
220
+ inset: 0;
221
+ background: rgba(0, 0, 0, 0.7);
222
+ backdrop-filter: blur(4px);
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ z-index: 1000;
227
+ }
228
+
229
+ .modal-content {
230
+ width: 90%;
231
+ max-width: 400px;
232
+ padding: 30px;
233
+ }
234
+
235
+ .auth-form {
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 15px;
239
+ margin: 20px 0;
240
+ }
241
+
242
+ .auth-form input {
243
+ background: rgba(255, 255, 255, 0.05);
244
+ border: 1px solid var(--glass-border);
245
+ border-radius: 12px;
246
+ padding: 12px;
247
+ color: white;
248
+ outline: none;
249
+ }
250
+
251
+ .btn-primary {
252
+ background: var(--primary);
253
+ color: white;
254
+ border: none;
255
+ padding: 12px;
256
+ border-radius: 12px;
257
+ font-weight: bold;
258
+ cursor: pointer;
259
+ transition: 0.2s;
260
+ }
261
+
262
+ .btn-primary:hover { filter: brightness(1.1); }
263
+
264
+ /* --- Animations --- */
265
+ @keyframes fadeIn {
266
+ from { opacity: 0; transform: translateY(10px); }
267
+ to { opacity: 1; transform: translateY(0); }
268
+ }
269
 
270
  .hidden { display: none !important; }
271
+
272
+ .icon-btn {
273
+ background: transparent;
274
+ border: none;
275
+ color: var(--text-muted);
276
+ font-size: 1.2rem;
277
+ cursor: pointer;
278
+ padding: 8px;
279
+ transition: color 0.2s;
280
+ }
281
+
282
+ .icon-btn:hover { color: var(--primary); }
283
+
284
+ /* --- Mobile Fixes --- */
285
+ @media (max-width: 600px) {
286
+ #sidebar { width: 100%; }
287
+ .msg-bubble { font-size: 0.95rem; }
288
+ .glass-header h1 { font-size: 1.2rem; }
289
+ .sys-info { display: none; }
290
+ }
291
+
292
+ .generating-status {
293
+ font-size: 0.8rem;
294
+ color: var(--primary);
295
+ margin-bottom: 8px;
296
+ font-weight: bold;
297
+ }