Soltane777 commited on
Commit
7cdf1d4
·
verified ·
1 Parent(s): 1631029

Upload 11 files

Browse files
Files changed (5) hide show
  1. backend/utils.py +70 -70
  2. frontend/index.html +3 -2
  3. frontend/script.js +12 -32
  4. frontend/style.css +285 -288
  5. requirements.txt +20 -20
backend/utils.py CHANGED
@@ -1,71 +1,71 @@
1
- import fitz # pymupdf
2
- from docx import Document
3
- import pptx
4
- import os
5
- from typing import Optional
6
-
7
- def extract_text_from_pdf(file_path: str) -> Optional[str]:
8
- """
9
- استخراج النص من ملف PDF باستخدام pymupdf (أسرع من tika).
10
- """
11
- try:
12
- doc = fitz.open(file_path)
13
- text = ""
14
- for page in doc:
15
- text += page.get_text()
16
- return text.strip() if text else None
17
- except Exception as e:
18
- print(f"Error reading PDF: {e}")
19
- return None
20
-
21
- def extract_text_from_docx(file_path: str) -> Optional[str]:
22
- """
23
- استخراج النص من ملف Word (DOCX).
24
- """
25
- try:
26
- doc = Document(file_path)
27
- return "\n".join([p.text for p in doc.paragraphs if p.text.strip()])
28
- except Exception as e:
29
- print(f"Error reading DOCX: {e}")
30
- return None
31
-
32
- def extract_text_from_pptx(file_path: str) -> Optional[str]:
33
- """
34
- استخراج النص من ملف PowerPoint (PPTX).
35
- """
36
- try:
37
- presentation = pptx.Presentation(file_path)
38
- text = []
39
- for slide in presentation.slides:
40
- for shape in slide.shapes:
41
- if hasattr(shape, "text"):
42
- text.append(shape.text)
43
- return "\n".join(text) if text else None
44
- except Exception as e:
45
- print(f"Error reading PPTX: {e}")
46
- return None
47
-
48
- def extract_text_from_document(file_path: str) -> Optional[str]:
49
- """
50
- دالة موحدة لاستخراج النص من أي مستند (PDF/DOCX/PPTX/TXT).
51
- """
52
- if not os.path.exists(file_path):
53
- print(f"File not found: {file_path}")
54
- return None
55
-
56
- if file_path.lower().endswith('.pdf'):
57
- return extract_text_from_pdf(file_path)
58
- elif file_path.lower().endswith('.docx'):
59
- return extract_text_from_docx(file_path)
60
- elif file_path.lower().endswith('.pptx'):
61
- return extract_text_from_pptx(file_path)
62
- elif file_path.lower().endswith('.txt'):
63
- try:
64
- with open(file_path, 'r', encoding='utf-8') as f:
65
- return f.read()
66
- except Exception as e:
67
- print(f"Error reading TXT: {e}")
68
- return None
69
- else:
70
- print(f"Unsupported file format: {file_path}")
71
  return None
 
1
+ import fitz # pymupdf
2
+ from docx import Document
3
+ import pptx
4
+ import os
5
+ from typing import Optional
6
+
7
+ def extract_text_from_pdf(file_path: str) -> Optional[str]:
8
+ """
9
+ استخراج النص من ملف PDF باستخدام pymupdf (أسرع من tika).
10
+ """
11
+ try:
12
+ doc = fitz.open(file_path)
13
+ text = ""
14
+ for page in doc:
15
+ text += page.get_text()
16
+ return text.strip() if text else None
17
+ except Exception as e:
18
+ print(f"Error reading PDF: {e}")
19
+ return None
20
+
21
+ def extract_text_from_docx(file_path: str) -> Optional[str]:
22
+ """
23
+ استخراج النص من ملف Word (DOCX).
24
+ """
25
+ try:
26
+ doc = Document(file_path)
27
+ return "\n".join([p.text for p in doc.paragraphs if p.text.strip()])
28
+ except Exception as e:
29
+ print(f"Error reading DOCX: {e}")
30
+ return None
31
+
32
+ def extract_text_from_pptx(file_path: str) -> Optional[str]:
33
+ """
34
+ استخراج النص من ملف PowerPoint (PPTX).
35
+ """
36
+ try:
37
+ presentation = pptx.Presentation(file_path)
38
+ text = []
39
+ for slide in presentation.slides:
40
+ for shape in slide.shapes:
41
+ if hasattr(shape, "text"):
42
+ text.append(shape.text)
43
+ return "\n".join(text) if text else None
44
+ except Exception as e:
45
+ print(f"Error reading PPTX: {e}")
46
+ return None
47
+
48
+ def extract_text_from_document(file_path: str) -> Optional[str]:
49
+ """
50
+ دالة موحدة لاستخراج النص من أي مستند (PDF/DOCX/PPTX/TXT).
51
+ """
52
+ if not os.path.exists(file_path):
53
+ print(f"File not found: {file_path}")
54
+ return None
55
+
56
+ if file_path.lower().endswith('.pdf'):
57
+ return extract_text_from_pdf(file_path)
58
+ elif file_path.lower().endswith('.docx'):
59
+ return extract_text_from_docx(file_path)
60
+ elif file_path.lower().endswith('.pptx'):
61
+ return extract_text_from_pptx(file_path)
62
+ elif file_path.lower().endswith('.txt'):
63
+ try:
64
+ with open(file_path, 'r', encoding='utf-8') as f:
65
+ return f.read()
66
+ except Exception as e:
67
+ print(f"Error reading TXT: {e}")
68
+ return None
69
+ else:
70
+ print(f"Unsupported file format: {file_path}")
71
  return None
frontend/index.html CHANGED
@@ -5,7 +5,7 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>AI Web Application</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <link rel="stylesheet" href="style.css">
9
  </head>
10
  <body class="bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-900 dark:to-gray-800 font-sans min-h-screen transition-colors duration-200">
11
  <div class="max-w-6xl mx-auto mt-6 p-6">
@@ -211,6 +211,7 @@
211
  </footer>
212
  </div>
213
 
214
- <script src="script.js"></script>
 
215
  </body>
216
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>AI Web Application</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="/static/style.css">
9
  </head>
10
  <body class="bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-900 dark:to-gray-800 font-sans min-h-screen transition-colors duration-200">
11
  <div class="max-w-6xl mx-auto mt-6 p-6">
 
211
  </footer>
212
  </div>
213
 
214
+ <script src="/static/script.js"></script>
215
+ <script src="/static/theme-fix.js"></script>
216
  </body>
217
  </html>
frontend/script.js CHANGED
@@ -1,6 +1,5 @@
1
  // Tab switching functionality
2
  document.addEventListener("DOMContentLoaded", () => {
3
- localStorage.removeItem("theme")
4
  // Initialize theme
5
  initTheme()
6
 
@@ -112,38 +111,19 @@ document.addEventListener("DOMContentLoaded", () => {
112
 
113
  // Theme functions
114
  function initTheme() {
115
- const savedTheme = localStorage.getItem("theme");
116
-
117
- // افتراضي: الوضع الفاتح إذا لم يكن هناك تفضيل محدد
118
- // يمكنك تغيير هذا إذا أردت
119
-
120
- if (savedTheme === "dark" || (!savedTheme && !defaultLight && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
121
- document.documentElement.classList.add("dark");
122
- document.getElementById("sun-icon")?.classList.remove("hidden");
123
- document.getElementById("moon-icon")?.classList.add("hidden");
124
  } else {
125
- document.documentElement.classList.remove("dark");
126
- document.getElementById("sun-icon")?.classList.add("hidden");
127
- document.getElementById("moon-icon")?.classList.remove("hidden");
128
- }
129
- }
130
-
131
- function toggleTheme() {
132
- document.documentElement.classList.toggle("dark")
133
-
134
- const sunIcon = document.getElementById("sun-icon")
135
- const moonIcon = document.getElementById("moon-icon")
136
-
137
- if (document.documentElement.classList.contains("dark")) {
138
- sunIcon?.classList.remove("hidden")
139
- moonIcon?.classList.add("hidden")
140
- localStorage.setItem("theme", "dark")
141
- showNotification("Dark mode activated")
142
- } else {
143
- sunIcon?.classList.add("hidden")
144
- moonIcon?.classList.remove("hidden")
145
- localStorage.setItem("theme", "light")
146
- showNotification("Light mode activated")
147
  }
148
  }
149
 
 
1
  // Tab switching functionality
2
  document.addEventListener("DOMContentLoaded", () => {
 
3
  // Initialize theme
4
  initTheme()
5
 
 
111
 
112
  // Theme functions
113
  function initTheme() {
114
+ // Check if user preference exists in localStorage
115
+ const savedTheme = localStorage.getItem("theme")
116
+
117
+ if (savedTheme === "dark" || (!savedTheme && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
118
+ // Set dark mode
119
+ document.documentElement.classList.add("dark")
120
+ document.getElementById("sun-icon")?.classList.remove("hidden")
121
+ document.getElementById("moon-icon")?.classList.add("hidden")
 
122
  } else {
123
+ // Set light mode
124
+ document.documentElement.classList.remove("dark")
125
+ document.getElementById("sun-icon")?.classList.add("hidden")
126
+ document.getElementById("moon-icon")?.classList.remove("hidden")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  }
128
  }
129
 
frontend/style.css CHANGED
@@ -1,289 +1,286 @@
1
- /* Add these styles to ensure dark mode works properly */
2
- :root {
3
- color-scheme: light;
4
- }
5
-
6
- html.dark {
7
- color-scheme: dark;
8
- }
9
-
10
- /* Make sure transitions are smooth */
11
- html {
12
- transition: background-color 0.3s ease, color 0.3s ease;
13
- }
14
-
15
- @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap");
16
-
17
- body {
18
- font-family: "Poppins", Arial, sans-serif;
19
- margin: 0;
20
- padding: 0;
21
- }
22
-
23
- /* Ensure dark mode styles are applied */
24
- html.dark body {
25
- background-color: #1a202c;
26
- color: #f7fafc;
27
- }
28
-
29
- .tab-button.active {
30
- color: #7c3aed;
31
- border-bottom: 2px solid #7c3aed;
32
- }
33
-
34
- html.dark .tab-button.active {
35
- color: #a78bfa;
36
- border-bottom: 2px solid #a78bfa;
37
- }
38
-
39
- .tab-button:not(.active) {
40
- border-bottom: 2px solid transparent;
41
- }
42
-
43
- #output {
44
- font-family: "Poppins", Arial, sans-serif;
45
- }
46
-
47
- /* Custom scrollbar */
48
- ::-webkit-scrollbar {
49
- width: 8px;
50
- height: 8px;
51
- }
52
-
53
- ::-webkit-scrollbar-track {
54
- background: #f1f1f1;
55
- }
56
-
57
- html.dark ::-webkit-scrollbar-track {
58
- background: #374151;
59
- }
60
-
61
- ::-webkit-scrollbar-thumb {
62
- background: #c4b5fd;
63
- border-radius: 10px;
64
- }
65
-
66
- ::-webkit-scrollbar-thumb:hover {
67
- background: #a78bfa;
68
- }
69
-
70
- /* Animation for notifications */
71
- @keyframes fadeIn {
72
- from {
73
- opacity: 0;
74
- transform: translateY(-10px);
75
- }
76
- to {
77
- opacity: 1;
78
- transform: translateY(0);
79
- }
80
- }
81
-
82
- .notification {
83
- animation: fadeIn 0.3s ease-out forwards;
84
- }
85
-
86
- /* Language selection styles */
87
- #translationOptions {
88
- transition: all 0.3s ease;
89
- }
90
-
91
- #translationOptions.hidden {
92
- display: none;
93
- }
94
-
95
- select {
96
- appearance: none;
97
- background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
98
- background-repeat: no-repeat;
99
- background-position: right 0.7rem center;
100
- background-size: 1em;
101
- padding-right: 2.5rem;
102
- }
103
-
104
- /* Formatted output styles */
105
- .formatted-output {
106
- display: flex;
107
- flex-direction: column;
108
- gap: 16px;
109
- width: 100%;
110
- }
111
-
112
- .result-header {
113
- margin-bottom: 8px;
114
- }
115
-
116
- .result-header h3 {
117
- font-size: 18px;
118
- font-weight: 600;
119
- color: #4b5563;
120
- margin: 0;
121
- padding-bottom: 8px;
122
- border-bottom: 1px solid #e5e7eb;
123
- }
124
-
125
- html.dark .result-header h3 {
126
- color: #e5e7eb;
127
- border-bottom: 1px solid #4b5563;
128
- }
129
-
130
- .text-box {
131
- background-color: white;
132
- border-radius: 8px;
133
- border: 1px solid #e5e7eb;
134
- overflow: hidden;
135
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
136
- }
137
-
138
- html.dark .text-box {
139
- background-color: #1f2937;
140
- border: 1px solid #374151;
141
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
142
- }
143
-
144
- .text-box-header {
145
- background-color: #f9fafb;
146
- padding: 8px 12px;
147
- font-weight: 500;
148
- color: #4b5563;
149
- border-bottom: 1px solid #e5e7eb;
150
- font-size: 14px;
151
- }
152
-
153
- html.dark .text-box-header {
154
- background-color: #374151;
155
- color: #e5e7eb;
156
- border-bottom: 1px solid #4b5563;
157
- }
158
-
159
- .text-content {
160
- padding: 12px 16px;
161
- line-height: 1.6;
162
- color: #1f2937;
163
- }
164
-
165
- html.dark .text-content {
166
- color: #f3f4f6;
167
- }
168
-
169
- .original .text-box-header {
170
- background-color: #eff6ff;
171
- color: #1e40af;
172
- }
173
-
174
- html.dark .original .text-box-header {
175
- background-color: #1e3a8a;
176
- color: #93c5fd;
177
- }
178
-
179
- .translated .text-box-header {
180
- background-color: #f0fdf4;
181
- color: #166534;
182
- }
183
-
184
- html.dark .translated .text-box-header {
185
- background-color: #14532d;
186
- color: #86efac;
187
- }
188
-
189
- .question .text-box-header {
190
- background-color: #fff7ed;
191
- color: #9a3412;
192
- }
193
-
194
- html.dark .question .text-box-header {
195
- background-color: #7c2d12;
196
- color: #fdba74;
197
- }
198
-
199
- .answer .text-box-header {
200
- background-color: #ecfdf5;
201
- color: #065f46;
202
- }
203
-
204
- html.dark .answer .text-box-header {
205
- background-color: #064e3b;
206
- color: #6ee7b7;
207
- }
208
-
209
- .code-content {
210
- padding: 12px 16px;
211
- font-family: monospace;
212
- white-space: pre-wrap;
213
- background-color: #f8fafc;
214
- color: #334155;
215
- line-height: 1.5;
216
- font-size: 14px;
217
- overflow-x: auto;
218
- }
219
-
220
- html.dark .code-content {
221
- background-color: #0f172a;
222
- color: #e2e8f0;
223
- }
224
-
225
- .error-message {
226
- color: #b91c1c;
227
- padding: 12px;
228
- background-color: #fee2e2;
229
- border-radius: 6px;
230
- font-weight: 500;
231
- }
232
-
233
- html.dark .error-message {
234
- color: #fca5a5;
235
- background-color: #7f1d1d;
236
- }
237
-
238
- /* Responsive adjustments */
239
- @media (max-width: 640px) {
240
- .text-box-header {
241
- padding: 6px 10px;
242
- font-size: 13px;
243
- }
244
-
245
- .text-content {
246
- padding: 10px 12px;
247
- font-size: 14px;
248
- }
249
- }
250
-
251
- /* Theme toggle button animation */
252
- #theme-toggle {
253
- cursor: pointer;
254
- transition: transform 0.2s ease, background-color 0.2s ease;
255
- }
256
-
257
- #theme-toggle:hover {
258
- background-color: rgba(255, 255, 255, 0.3);
259
- }
260
-
261
- #theme-toggle:active {
262
- transform: scale(0.9);
263
- }
264
-
265
- /* History panel styles */
266
- #historyPanel {
267
- box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
268
- }
269
-
270
- html.dark #historyPanel {
271
- box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
272
- }
273
-
274
- /* Text-to-speech button styles */
275
- .text-to-speech-btn {
276
- transition: all 0.2s ease;
277
- }
278
-
279
- .text-to-speech-btn:hover svg {
280
- color: #7c3aed;
281
- }
282
-
283
- html.dark .text-to-speech-btn:hover svg {
284
- color: #a78bfa;
285
- }
286
-
287
-
288
-
289
 
 
1
+ /* Add these styles to ensure dark mode works properly */
2
+ :root {
3
+ color-scheme: light;
4
+ }
5
+
6
+ html.dark {
7
+ color-scheme: dark;
8
+ }
9
+
10
+ /* Make sure transitions are smooth */
11
+ html {
12
+ transition: background-color 0.3s ease, color 0.3s ease;
13
+ }
14
+
15
+ @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap");
16
+
17
+ body {
18
+ font-family: "Poppins", Arial, sans-serif;
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+
23
+ /* Ensure dark mode styles are applied */
24
+ html.dark body {
25
+ background-color: #1a202c;
26
+ color: #f7fafc;
27
+ }
28
+
29
+ .tab-button.active {
30
+ color: #7c3aed;
31
+ border-bottom: 2px solid #7c3aed;
32
+ }
33
+
34
+ html.dark .tab-button.active {
35
+ color: #a78bfa;
36
+ border-bottom: 2px solid #a78bfa;
37
+ }
38
+
39
+ .tab-button:not(.active) {
40
+ border-bottom: 2px solid transparent;
41
+ }
42
+
43
+ #output {
44
+ font-family: "Poppins", Arial, sans-serif;
45
+ }
46
+
47
+ /* Custom scrollbar */
48
+ ::-webkit-scrollbar {
49
+ width: 8px;
50
+ height: 8px;
51
+ }
52
+
53
+ ::-webkit-scrollbar-track {
54
+ background: #f1f1f1;
55
+ }
56
+
57
+ html.dark ::-webkit-scrollbar-track {
58
+ background: #374151;
59
+ }
60
+
61
+ ::-webkit-scrollbar-thumb {
62
+ background: #c4b5fd;
63
+ border-radius: 10px;
64
+ }
65
+
66
+ ::-webkit-scrollbar-thumb:hover {
67
+ background: #a78bfa;
68
+ }
69
+
70
+ /* Animation for notifications */
71
+ @keyframes fadeIn {
72
+ from {
73
+ opacity: 0;
74
+ transform: translateY(-10px);
75
+ }
76
+ to {
77
+ opacity: 1;
78
+ transform: translateY(0);
79
+ }
80
+ }
81
+
82
+ .notification {
83
+ animation: fadeIn 0.3s ease-out forwards;
84
+ }
85
+
86
+ /* Language selection styles */
87
+ #translationOptions {
88
+ transition: all 0.3s ease;
89
+ }
90
+
91
+ #translationOptions.hidden {
92
+ display: none;
93
+ }
94
+
95
+ select {
96
+ appearance: none;
97
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
98
+ background-repeat: no-repeat;
99
+ background-position: right 0.7rem center;
100
+ background-size: 1em;
101
+ padding-right: 2.5rem;
102
+ }
103
+
104
+ /* Formatted output styles */
105
+ .formatted-output {
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 16px;
109
+ width: 100%;
110
+ }
111
+
112
+ .result-header {
113
+ margin-bottom: 8px;
114
+ }
115
+
116
+ .result-header h3 {
117
+ font-size: 18px;
118
+ font-weight: 600;
119
+ color: #4b5563;
120
+ margin: 0;
121
+ padding-bottom: 8px;
122
+ border-bottom: 1px solid #e5e7eb;
123
+ }
124
+
125
+ html.dark .result-header h3 {
126
+ color: #e5e7eb;
127
+ border-bottom: 1px solid #4b5563;
128
+ }
129
+
130
+ .text-box {
131
+ background-color: white;
132
+ border-radius: 8px;
133
+ border: 1px solid #e5e7eb;
134
+ overflow: hidden;
135
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
136
+ }
137
+
138
+ html.dark .text-box {
139
+ background-color: #1f2937;
140
+ border: 1px solid #374151;
141
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
142
+ }
143
+
144
+ .text-box-header {
145
+ background-color: #f9fafb;
146
+ padding: 8px 12px;
147
+ font-weight: 500;
148
+ color: #4b5563;
149
+ border-bottom: 1px solid #e5e7eb;
150
+ font-size: 14px;
151
+ }
152
+
153
+ html.dark .text-box-header {
154
+ background-color: #374151;
155
+ color: #e5e7eb;
156
+ border-bottom: 1px solid #4b5563;
157
+ }
158
+
159
+ .text-content {
160
+ padding: 12px 16px;
161
+ line-height: 1.6;
162
+ color: #1f2937;
163
+ }
164
+
165
+ html.dark .text-content {
166
+ color: #f3f4f6;
167
+ }
168
+
169
+ .original .text-box-header {
170
+ background-color: #eff6ff;
171
+ color: #1e40af;
172
+ }
173
+
174
+ html.dark .original .text-box-header {
175
+ background-color: #1e3a8a;
176
+ color: #93c5fd;
177
+ }
178
+
179
+ .translated .text-box-header {
180
+ background-color: #f0fdf4;
181
+ color: #166534;
182
+ }
183
+
184
+ html.dark .translated .text-box-header {
185
+ background-color: #14532d;
186
+ color: #86efac;
187
+ }
188
+
189
+ .question .text-box-header {
190
+ background-color: #fff7ed;
191
+ color: #9a3412;
192
+ }
193
+
194
+ html.dark .question .text-box-header {
195
+ background-color: #7c2d12;
196
+ color: #fdba74;
197
+ }
198
+
199
+ .answer .text-box-header {
200
+ background-color: #ecfdf5;
201
+ color: #065f46;
202
+ }
203
+
204
+ html.dark .answer .text-box-header {
205
+ background-color: #064e3b;
206
+ color: #6ee7b7;
207
+ }
208
+
209
+ .code-content {
210
+ padding: 12px 16px;
211
+ font-family: monospace;
212
+ white-space: pre-wrap;
213
+ background-color: #f8fafc;
214
+ color: #334155;
215
+ line-height: 1.5;
216
+ font-size: 14px;
217
+ overflow-x: auto;
218
+ }
219
+
220
+ html.dark .code-content {
221
+ background-color: #0f172a;
222
+ color: #e2e8f0;
223
+ }
224
+
225
+ .error-message {
226
+ color: #b91c1c;
227
+ padding: 12px;
228
+ background-color: #fee2e2;
229
+ border-radius: 6px;
230
+ font-weight: 500;
231
+ }
232
+
233
+ html.dark .error-message {
234
+ color: #fca5a5;
235
+ background-color: #7f1d1d;
236
+ }
237
+
238
+ /* Responsive adjustments */
239
+ @media (max-width: 640px) {
240
+ .text-box-header {
241
+ padding: 6px 10px;
242
+ font-size: 13px;
243
+ }
244
+
245
+ .text-content {
246
+ padding: 10px 12px;
247
+ font-size: 14px;
248
+ }
249
+ }
250
+
251
+ /* Theme toggle button animation */
252
+ #theme-toggle {
253
+ cursor: pointer;
254
+ transition: transform 0.2s ease, background-color 0.2s ease;
255
+ }
256
+
257
+ #theme-toggle:hover {
258
+ background-color: rgba(255, 255, 255, 0.3);
259
+ }
260
+
261
+ #theme-toggle:active {
262
+ transform: scale(0.9);
263
+ }
264
+
265
+ /* History panel styles */
266
+ #historyPanel {
267
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
268
+ }
269
+
270
+ html.dark #historyPanel {
271
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
272
+ }
273
+
274
+ /* Text-to-speech button styles */
275
+ .text-to-speech-btn {
276
+ transition: all 0.2s ease;
277
+ }
278
+
279
+ .text-to-speech-btn:hover svg {
280
+ color: #7c3aed;
281
+ }
282
+
283
+ html.dark .text-to-speech-btn:hover svg {
284
+ color: #a78bfa;
285
+ }
 
 
 
286
 
requirements.txt CHANGED
@@ -1,21 +1,21 @@
1
- # معالجة النصوص والمستندات
2
- pdfminer.six==20221105 # لاستخراج النص من PDF (بديل خفيف)
3
- python-docx==0.8.11 # لقراءة ملفات Word
4
- pymupdf==1.23.21 # بديل أسرع لـ PDF (اختياري)
5
-
6
- # معالجة ملفات أخرى
7
- python-pptx==0.6.23 # لقراءة ملفات PowerPoint (إذا كنت تحتاجه)
8
-
9
- # التعامل مع Hugging Face APIs
10
- requests==2.31.0 # لطلبات HTTP
11
-
12
- # FastAPI (إذا كنت تبني واجهة API)
13
- fastapi==0.109.2
14
- uvicorn==0.27.0
15
- python-multipart==0.0.6 # لتحميل الملفات
16
-
17
- # إدارة المتغيرات البيئية
18
- python-dotenv==1.0.0 # لتحميل HUGGING_FACE_API_KEY
19
-
20
- # التعامل مع الأخطاء (اختياري)
21
  loguru==0.7.2 # لتسجيل الأخطاء بشكل أنيق
 
1
+ # معالجة النصوص والمستندات
2
+ pdfminer.six==20221105 # لاستخراج النص من PDF (بديل خفيف)
3
+ python-docx==0.8.11 # لقراءة ملفات Word
4
+ pymupdf==1.23.21 # بديل أسرع لـ PDF (اختياري)
5
+
6
+ # معالجة ملفات أخرى
7
+ python-pptx==0.6.23 # لقراءة ملفات PowerPoint (إذا كنت تحتاجه)
8
+
9
+ # التعامل مع Hugging Face APIs
10
+ requests==2.31.0 # لطلبات HTTP
11
+
12
+ # FastAPI (إذا كنت تبني واجهة API)
13
+ fastapi==0.109.2
14
+ uvicorn==0.27.0
15
+ python-multipart==0.0.6 # لتحميل الملفات
16
+
17
+ # إدارة المتغيرات البيئية
18
+ python-dotenv==1.0.0 # لتحميل HUGGING_FACE_API_KEY
19
+
20
+ # التعامل مع الأخطاء (اختياري)
21
  loguru==0.7.2 # لتسجيل الأخطاء بشكل أنيق