Hamid191 commited on
Commit
3f30aae
·
verified ·
1 Parent(s): 72ec186

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +15 -0
  2. app.py +150 -0
  3. index.html +390 -0
  4. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.10-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /code
6
+
7
+ # Copy the requirements file and install dependencies
8
+ COPY requirements.txt requirements.txt
9
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
10
+
11
+ # Copy the rest of the application code
12
+ COPY . .
13
+
14
+ # Command to run the application using gunicorn
15
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ from flask import Flask, render_template, request, jsonify
4
+ from groq import Groq
5
+ from dotenv import load_dotenv
6
+
7
+ # Load environment variables from .env file
8
+ load_dotenv()
9
+
10
+ # Tell Flask that the template folder is the current directory ('.')
11
+ app = Flask(__name__, template_folder='.')
12
+
13
+ # --- Configuration ---
14
+ try:
15
+ groq_client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
16
+ except Exception as e:
17
+ print(f"Error initializing Groq client: {e}")
18
+ groq_client = None
19
+
20
+ # API for Tafsir (Commentary)
21
+ TAFSIR_API_BASE_URL = "https://cdn.jsdelivr.net/gh/spa5k/tafsir_api@main/tafsir"
22
+ TAFSIR_SOURCES = {
23
+ "context": "en-kashf-al-asrar-tafsir",
24
+ "classical": "en-tafisr-ibn-kathir",
25
+ "modern": "en-tafsir-maarif-ul-quran"
26
+ }
27
+ DATA_UNAVAILABLE_MESSAGE = "Information from this source is currently unavailable or not found for this verse."
28
+
29
+ # API for Quran Text and Translation
30
+ QURAN_API_BASE_URL = "http://api.alquran.cloud/v1/ayah"
31
+
32
+ def get_ayah_text(surah: int, ayah: int) -> str:
33
+ """Fetches the English translation of a specific ayah."""
34
+ url = f"{QURAN_API_BASE_URL}/{surah}:{ayah}/en.asad"
35
+ try:
36
+ response = requests.get(url, timeout=10)
37
+ response.raise_for_status()
38
+ data = response.json()
39
+ if data.get('code') == 200 and data.get('data', {}).get('text'):
40
+ return data['data']['text']
41
+ else:
42
+ return "Could not retrieve the translation for this verse."
43
+ except requests.exceptions.RequestException as e:
44
+ print(f"Error fetching Ayah text: {e}")
45
+ return "Could not retrieve the translation due to a network error."
46
+
47
+ def get_tafsir_data(surah: int, ayah: int) -> dict:
48
+ """Fetches commentary with robust error handling."""
49
+ collected_data = {}
50
+ for source_name, source_slug in TAFSIR_SOURCES.items():
51
+ url = f"{TAFSIR_API_BASE_URL}/{source_slug}/{surah}/{ayah}.json"
52
+ try:
53
+ response = requests.get(url, timeout=10)
54
+ response.raise_for_status()
55
+ data = response.json()
56
+ text = data.get("text", "").strip()
57
+ collected_data[source_name] = text if text else DATA_UNAVAILABLE_MESSAGE
58
+ except requests.exceptions.RequestException as e:
59
+ print(f"Error fetching Tafsir from {source_slug}: {e}")
60
+ collected_data[source_name] = DATA_UNAVAILABLE_MESSAGE
61
+ return collected_data
62
+
63
+ def generate_explanation_prompt(surah: int, ayah: int, ayah_text: str, tafsir_data: dict) -> str:
64
+ """
65
+ Constructs a more direct and forceful prompt to ensure the Ayah is printed.
66
+ """
67
+ # --- MODIFICATION START: This entire function is rewritten for clarity and forcefulness ---
68
+
69
+ # Start with a clear definition of the persona and overall goal.
70
+ prompt = f"""You are a helpful and knowledgeable assistant for Quranic studies. Your primary task is to provide a clear, multi-source explanation for Surah {surah}, Ayah {ayah}.
71
+
72
+ **Your response MUST be structured in the following order:**
73
+
74
+ **1. The Verse:**
75
+ - Start with the heading "### The Verse (Surah {surah}: Ayah {ayah})".
76
+ - Immediately after the heading, quote the English translation of the verse provided below.
77
+
78
+ **2. The Explanation:**
79
+ - Based on the data provided, generate a detailed explanation. You must attribute every piece of information to its source using phrases like "According to Al-Wahidi...", "Ibn Kathir explains that...", etc.
80
+ - If the 'Shan-e-Nazool' (reason for revelation) from Al-Wahidi is available, present it first under the heading "### Shan-e-Nazool (Reason for Revelation)".
81
+ - If it is unavailable, DO NOT mention the unavailability. Simply proceed to the next section.
82
+ - Present the commentary from Tafsir Ibn Kathir under the heading "### Classical Commentary: Tafsir Ibn Kathir".
83
+ - Present the commentary from Maarif-ul-Quran under the heading "### Modern Commentary: Maarif-ul-Quran".
84
+
85
+ **3. Summary:**
86
+ - Conclude with a brief summary under the heading "### Summary".
87
+
88
+ ---
89
+ **DATA FOR YOUR TASK:**
90
+
91
+ **[Verse Translation]:**
92
+ {ayah_text}
93
+
94
+ **[Al-Wahidi's Asbab Al-Nuzul]:**
95
+ {tafsir_data.get('context')}
96
+
97
+ **[Tafsir Ibn Kathir]:**
98
+ {tafsir_data.get('classical')}
99
+
100
+ **[Maarif-ul-Quran]:**
101
+ {tafsir_data.get('modern')}
102
+ ---
103
+
104
+ Begin your response now.
105
+ """
106
+ # --- MODIFICATION END ---
107
+ return prompt
108
+
109
+ @app.route('/')
110
+ def index():
111
+ return render_template('index.html')
112
+
113
+ @app.route('/explain', methods=['POST'])
114
+ def explain_ayah():
115
+ if not groq_client:
116
+ return jsonify({"error": "Groq client is not initialized. Check API key."}), 500
117
+
118
+ data = request.get_json()
119
+ if not data or 'surah' not in data or 'ayah' not in data:
120
+ return jsonify({"error": "Missing Surah or Ayah number"}), 400
121
+
122
+ try:
123
+ surah = int(data['surah'])
124
+ ayah = int(data['ayah'])
125
+ except (ValueError, TypeError):
126
+ return jsonify({"error": "Surah and Ayah must be numbers"}), 400
127
+
128
+ ayah_text = get_ayah_text(surah, ayah)
129
+ tafsir_data = get_tafsir_data(surah, ayah)
130
+ prompt = generate_explanation_prompt(surah, ayah, ayah_text, tafsir_data)
131
+
132
+ try:
133
+ chat_completion = groq_client.chat.completions.create(
134
+ messages=[{"role": "user", "content": prompt}],
135
+ # MODIFICATION: Using the model name that is confirmed to be available and powerful.
136
+ model="llama-3.3-70b-versatile",
137
+ temperature=0.6, # Slightly reduced for more factual consistency
138
+ max_tokens=2048,
139
+ )
140
+ explanation = chat_completion.choices[0].message.content
141
+ return jsonify({"explanation": explanation})
142
+ except Exception as e:
143
+ print(f"Error calling Groq API: {e}")
144
+ return jsonify({"error": f"Failed to get explanation from the language model: {e}"}), 500
145
+
146
+ if __name__ == '__main__':
147
+ app.run(debug=True)
148
+
149
+
150
+ #llama-3.3-70b-versatile
index.html ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Quran Explainer</title>
7
+
8
+ <!-- Google Font Import: Inter -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
12
+
13
+ <!-- Marked.js for Markdown rendering -->
14
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
15
+
16
+ <style>
17
+ /* --- Root Variables for Color Palette --- */
18
+ :root {
19
+ --bg-dark: #1a202c;
20
+ --card-bg: #2d3748;
21
+ --text-primary: #edf2f7;
22
+ --text-secondary: #a0aec0;
23
+ --accent-color: #d69e2e;
24
+ --border-color: #4a5568;
25
+ --skeleton-color: #4a5568;
26
+ }
27
+
28
+ /* --- General Body and Layout Styling --- */
29
+ body {
30
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
31
+ line-height: 1.6;
32
+ color: var(--text-primary);
33
+ background-color: var(--bg-dark);
34
+ margin: 0;
35
+ padding: 20px;
36
+ display: flex;
37
+ justify-content: center;
38
+ align-items: flex-start;
39
+ min-height: 100vh;
40
+ }
41
+
42
+ /* --- Main Container Card --- */
43
+ .container {
44
+ width: 100%;
45
+ max-width: 800px;
46
+ margin: 0 auto;
47
+ background: var(--card-bg);
48
+ padding: 40px;
49
+ border-radius: 12px;
50
+ border: 1px solid var(--border-color);
51
+ box-shadow: 0 10px 30px rgba(0,0,0,0.25);
52
+ transition: all 0.3s ease;
53
+ animation: fadeInUp 0.6s ease-out;
54
+ }
55
+
56
+ @keyframes fadeInUp {
57
+ from {
58
+ opacity: 0;
59
+ transform: translateY(20px);
60
+ }
61
+ to {
62
+ opacity: 1;
63
+ transform: translateY(0);
64
+ }
65
+ }
66
+
67
+ /* --- Typography Hierarchy --- */
68
+ h1 {
69
+ text-align: center;
70
+ color: var(--text-primary);
71
+ font-weight: 700;
72
+ font-size: 2.25rem;
73
+ margin: 0;
74
+ }
75
+
76
+ .subtitle {
77
+ text-align: center;
78
+ color: var(--text-secondary);
79
+ margin-top: 8px;
80
+ margin-bottom: 40px;
81
+ }
82
+
83
+ /* --- Form and Input Styling --- */
84
+ form {
85
+ display: grid;
86
+ grid-template-columns: 1fr 1fr auto;
87
+ gap: 15px;
88
+ margin-bottom: 30px;
89
+ }
90
+
91
+ input[type="number"] {
92
+ background-color: var(--bg-dark);
93
+ color: var(--text-primary);
94
+ border: 1px solid var(--border-color);
95
+ border-radius: 8px;
96
+ padding: 12px 15px;
97
+ font-size: 16px;
98
+ transition: border-color 0.3s, box-shadow 0.3s;
99
+ }
100
+
101
+ input[type="number"]::placeholder {
102
+ color: var(--text-secondary);
103
+ opacity: 0.7;
104
+ }
105
+
106
+ input[type="number"]:focus {
107
+ outline: none;
108
+ border-color: var(--accent-color);
109
+ box-shadow: 0 0 0 3px rgba(214, 158, 46, 0.2);
110
+ }
111
+
112
+ /* Remove number input spinners */
113
+ input::-webkit-outer-spin-button,
114
+ input::-webkit-inner-spin-button {
115
+ -webkit-appearance: none;
116
+ margin: 0;
117
+ }
118
+ input[type=number] {
119
+ -moz-appearance: textfield;
120
+ appearance: textfield;
121
+ }
122
+
123
+ button {
124
+ background-color: var(--accent-color);
125
+ color: #1a202c;
126
+ font-weight: 500;
127
+ border: none;
128
+ border-radius: 8px;
129
+ cursor: pointer;
130
+ font-size: 16px;
131
+ padding: 12px 25px;
132
+ transition: transform 0.2s, background-color 0.3s;
133
+ }
134
+
135
+ button:hover {
136
+ background-color: #e5b342;
137
+ transform: translateY(-2px);
138
+ box-shadow: 0 4px 12px rgba(214, 158, 46, 0.3);
139
+ }
140
+
141
+ button:active {
142
+ transform: translateY(0);
143
+ }
144
+
145
+ /* --- New Skeleton Loader --- */
146
+ .loader {
147
+ display: none; /* Controlled by JS */
148
+ padding: 20px;
149
+ border: 1px solid var(--border-color);
150
+ border-radius: 8px;
151
+ background-color: var(--card-bg);
152
+ }
153
+
154
+ .skeleton {
155
+ background-color: var(--skeleton-color);
156
+ border-radius: 4px;
157
+ animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
158
+ }
159
+ .skeleton-title { height: 28px; width: 40%; margin-bottom: 20px; }
160
+ .skeleton-line { height: 16px; margin-bottom: 12px; }
161
+ .skeleton-line-1 { width: 90%; }
162
+ .skeleton-line-2 { width: 100%; }
163
+ .skeleton-line-3 { width: 75%; }
164
+
165
+ @keyframes pulse {
166
+ 0%, 100% { opacity: 0.6; }
167
+ 50% { opacity: 1; }
168
+ }
169
+
170
+ /* --- Response Container and Content Styling --- */
171
+ #response-container {
172
+ margin-top: 20px;
173
+ opacity: 0;
174
+ transform: translateY(10px);
175
+ transition: opacity 0.5s ease, transform 0.5s ease;
176
+ }
177
+
178
+ /* Style for when JS makes the content visible */
179
+ #response-container:not(:empty) {
180
+ opacity: 1;
181
+ transform: translateY(0);
182
+ }
183
+
184
+ #response-container h1,
185
+ #response-container h2,
186
+ #response-container h3,
187
+ #response-container h4 {
188
+ color: var(--accent-color);
189
+ margin-top: 25px;
190
+ margin-bottom: 15px;
191
+ font-weight: 600;
192
+ }
193
+
194
+ #response-container h3 {
195
+ border-bottom: 1px solid var(--border-color);
196
+ padding-bottom: 8px;
197
+ }
198
+
199
+ #response-container p {
200
+ color: var(--text-secondary);
201
+ margin-bottom: 15px;
202
+ line-height: 1.7;
203
+ }
204
+
205
+ #response-container blockquote {
206
+ margin: 20px 0;
207
+ padding: 15px 20px;
208
+ background-color: var(--bg-dark);
209
+ border-left: 4px solid var(--accent-color);
210
+ font-style: italic;
211
+ color: var(--text-primary);
212
+ border-radius: 4px;
213
+ }
214
+
215
+ #response-container ul,
216
+ #response-container ol {
217
+ color: var(--text-secondary);
218
+ margin: 15px 0;
219
+ padding-left: 25px;
220
+ }
221
+
222
+ #response-container li {
223
+ margin-bottom: 8px;
224
+ line-height: 1.7;
225
+ }
226
+
227
+ #response-container strong {
228
+ color: var(--text-primary);
229
+ font-weight: 600;
230
+ }
231
+
232
+ #response-container em {
233
+ color: var(--text-secondary);
234
+ font-style: italic;
235
+ }
236
+
237
+ #response-container code {
238
+ background-color: var(--bg-dark);
239
+ color: var(--accent-color);
240
+ padding: 2px 6px;
241
+ border-radius: 4px;
242
+ font-size: 0.9em;
243
+ font-family: 'Courier New', monospace;
244
+ }
245
+
246
+ #response-container pre {
247
+ background-color: var(--bg-dark);
248
+ padding: 15px;
249
+ border-radius: 8px;
250
+ overflow-x: auto;
251
+ border: 1px solid var(--border-color);
252
+ margin: 15px 0;
253
+ }
254
+
255
+ #response-container pre code {
256
+ background-color: transparent;
257
+ padding: 0;
258
+ color: var(--text-primary);
259
+ }
260
+
261
+ #response-container hr {
262
+ border: none;
263
+ border-top: 1px solid var(--border-color);
264
+ margin: 25px 0;
265
+ }
266
+
267
+ #response-container .error {
268
+ color: #e53e3e;
269
+ font-weight: bold;
270
+ background-color: rgba(229, 62, 62, 0.1);
271
+ padding: 15px;
272
+ border-radius: 8px;
273
+ border-left: 4px solid #e53e3e;
274
+ }
275
+
276
+ /* --- Footer --- */
277
+ footer {
278
+ text-align: center;
279
+ margin-top: 40px;
280
+ padding-top: 20px;
281
+ border-top: 1px solid var(--border-color);
282
+ }
283
+
284
+ .disclaimer {
285
+ font-size: 0.8rem;
286
+ color: var(--text-secondary);
287
+ }
288
+
289
+ /* --- Responsive Design --- */
290
+ @media (max-width: 600px) {
291
+ body {
292
+ padding: 10px;
293
+ }
294
+ .container {
295
+ padding: 25px 20px;
296
+ }
297
+ h1 {
298
+ font-size: 1.75rem;
299
+ }
300
+ .subtitle {
301
+ font-size: 0.9rem;
302
+ }
303
+ form {
304
+ grid-template-columns: 1fr; /* Stack inputs and button */
305
+ }
306
+ button {
307
+ width: 100%;
308
+ }
309
+ #response-container h1,
310
+ #response-container h2,
311
+ #response-container h3,
312
+ #response-container h4 {
313
+ font-size: 1.2rem;
314
+ }
315
+ }
316
+
317
+ </style>
318
+ </head>
319
+ <body>
320
+ <br><br>
321
+ <main class="container">
322
+ <h1>Quran Explainer</h1>
323
+ <p class="subtitle">Enter a Surah and Ayah to receive a multi-source scholarly explanation.</p>
324
+
325
+ <form id="tafsir-form">
326
+ <input type="number" id="surah" name="surah" placeholder="Surah No." required>
327
+ <input type="number" id="ayah" name="ayah" placeholder="Ayah No." required>
328
+ <button type="submit">Get Explanation</button>
329
+ </form>
330
+
331
+ <!-- The new skeleton loader, still controlled by id="loader" -->
332
+ <div class="loader" id="loader">
333
+ <div class="skeleton skeleton-title"></div>
334
+ <div class="skeleton skeleton-line skeleton-line-1"></div>
335
+ <div class="skeleton skeleton-line skeleton-line-2"></div>
336
+ <div class="skeleton skeleton-line skeleton-line-3"></div>
337
+ </div>
338
+
339
+ <!-- The response container, which will be populated by JS -->
340
+ <div id="response-container"></div>
341
+
342
+ <footer>
343
+ <p class="disclaimer">This is an AI-powered tool for study and reference. Always consult with a qualified scholar for definitive religious guidance.
344
+ <br>GitHub repo <a href="https://github.com/spa5k/tafsir_api/tree/main">tafsir_api</a> is used to get the tasfir from different sources.
345
+ </p>
346
+ </footer>
347
+ </main>
348
+
349
+ <script>
350
+ // THIS SCRIPT BLOCK IS PRESERVED EXACTLY AS REQUIRED
351
+ document.getElementById('tafsir-form').addEventListener('submit', async function(event) {
352
+ event.preventDefault();
353
+ const surah = document.getElementById('surah').value;
354
+ const ayah = document.getElementById('ayah').value;
355
+ const responseContainer = document.getElementById('response-container');
356
+ const loader = document.getElementById('loader');
357
+
358
+ // Clear previous response and show loader
359
+ responseContainer.innerHTML = '';
360
+ // We remove the transition classes to reset the fade-in state
361
+ responseContainer.style.opacity = '0';
362
+ responseContainer.style.transform = 'translateY(10px)';
363
+ loader.style.display = 'block';
364
+
365
+ try {
366
+ const response = await fetch('/explain', {
367
+ method: 'POST',
368
+ headers: { 'Content-Type': 'application/json' },
369
+ body: JSON.stringify({ surah: surah, ayah: ayah })
370
+ });
371
+
372
+ if (!response.ok) {
373
+ const errorData = await response.json();
374
+ throw new Error(errorData.error || 'An unknown error occurred.');
375
+ }
376
+ const data = await response.json();
377
+ responseContainer.innerHTML = marked.parse(data.explanation);
378
+
379
+ } catch (error) {
380
+ responseContainer.innerHTML = `<p class="error">Error: ${error.message}</p>`;
381
+ } finally {
382
+ loader.style.display = 'none';
383
+ // Trigger the fade-in animation for the new content
384
+ responseContainer.style.opacity = '1';
385
+ responseContainer.style.transform = 'translateY(0)';
386
+ }
387
+ });
388
+ </script>
389
+ </body>
390
+ </html>
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Flask
2
+ requests
3
+ groq
4
+ python-dotenv