Tafseer added with llm model

This commit is contained in:
Akil 2025-06-27 23:19:25 +03:00
parent a08022ff5b
commit ce20960d77
8 changed files with 3422 additions and 1532 deletions

View File

@ -17,6 +17,12 @@ const nextConfig = {
* permanent: تعيينه على true يعني أن إعادة التوجيه دائمة (ستستخدم رمز الحالة 308). إذا كنت تريد أن تكون إعادة التوجيه مؤقتة، يمكنك تعيينه إلى false، وستستخدم رمز الحالة 307.
* @returns
*/
transpilePackages: [
'rc-util',
"rc-picker",
"rc-pagination",
"@ant-design/icons-svg"
],
async redirects() {
return [
{

4657
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,8 @@
},
"license": "MIT",
"dependencies": {
"antd": "^5.26.1",
"axios": "^1.10.0",
"i18next": "^24.2.3",
"next": "14.2.21",
"react": "^18",

View File

@ -0,0 +1,188 @@
import React, { useState, useRef, useEffect } from 'react';
import { Popover, Button } from 'antd';
import axios from 'axios';
import { t } from 'i18next';
const SelectableParagraph = ({ content, surahNo, verseNo, onDetailClick }) => {
const paragraphRef = useRef(null);
const [selection, setSelection] = useState({
text: '',
position: { x: 0, y: 0 },
visible: false
});
const handleTextSelect = (e) => {
const selection = window.getSelection();
// console.log('selection', selection.toString());
if (!selection || selection.toString().trim() === '') {
setSelection(prev => ({ ...prev, visible: false }));
return;
}
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
setSelection({
text: selection.toString(),
position: {
x: rect.left + (rect.width / 2),
y: rect.bottom
},
visible: true
});
};
useEffect(() => {
// console.log('selection', selection);
const handleClickOutside = (e) => {
if (!paragraphRef.current?.contains(e.target)) {
setSelection(prev => ({ ...prev, visible: false }));
}
};
document.addEventListener('mouseup', handleClickOutside);
return () => {
document.removeEventListener('mouseup', handleClickOutside);
};
}, []);
const handleAction = async () => {
setSelection(prev => ({ ...prev, visible: false }));
window.getSelection().empty();
const detail = {
selection: selection.text,
surahNo: surahNo,
verseNo: verseNo
}
onDetailClick(detail);
};
const popoverContent = (
<div style={{ display: 'flex', gap: '8px' }}>
{/* <Button size="small" onClick={() => handleAction('Copy')}>Copy</Button>
<Button size="small" onClick={() => handleAction('Analyze')}>Analyze</Button> */}
<Button size="small" onClick={() => handleAction()}>{t('surah.meta-tafsir')}</Button>
</div>
);
return (
<div ref={paragraphRef}>
<Popover
content={popoverContent}
open={selection.visible}
destroyOnHidden={true}
// onOpenChange={(visible) => setSelection(prev => ({ ...prev, visible }))}
trigger="click"
placement="bottom"
overlayStyle={{
position: 'fixed',
left: `${selection.position.x}px`,
top: `${selection.position.y}px`,
transform: 'translateX(-50%)',
animation: '0s fadeIn'
}}
// overlayInnerStyle={{ padding: '8px' }}
>
<div
onMouseUp={handleTextSelect}
// style={{ marginBottom: '20px', cursor: 'text' }}
onMouseDown={(e) => {
setSelection(prev => ({ ...prev, visible: false }));
}}
>
{content}
</div>
</Popover>
</div>
);
};
export default SelectableParagraph;
// import React, { useState, useEffect } from 'react';
// import { Popover, Button } from 'antd';
// const SelectableParagraph = ({ content, index }) => {
// const [selection, setSelection] = useState({
// text: '',
// position: { x: 0, y: 0 },
// visible: false
// });
// useEffect(() => {
// const handleMouseUp = (e) => {
// const selection = window.getSelection();
// const selectedText = selection.toString().trim();
// // Only handle if selection is within this paragraph
// if (!selectedText || !e.target.contains(selection.anchorNode)) {
// if (selection.visible) {
// setSelection(prev => ({ ...prev, visible: false }));
// }
// return;
// }
// const range = selection.getRangeAt(0);
// const rect = range.getBoundingClientRect();
// setSelection({
// text: selectedText,
// position: {
// x: rect.left + (rect.width / 2),
// y: rect.bottom
// },
// visible: true
// });
// };
// document.addEventListener('mouseup', handleMouseUp);
// return () => document.removeEventListener('mouseup', handleMouseUp);
// }, []);
// const handleAction = (action) => {
// console.log(`${action} text:`, selection.text);
// setSelection(prev => ({ ...prev, visible: false }));
// window.getSelection().empty();
// };
// const popoverContent = (
// <div style={{ display: 'flex', gap: '8px' }}>
// <Button size="small" onClick={() => handleAction('Copy')}>Copy</Button>
// <Button size="small" onClick={() => handleAction('Analyze')}>Analyze</Button>
// <Button size="small" onClick={() => handleAction('Describe')}>Describe</Button>
// </div>
// );
// return (
// <Popover
// content={popoverContent}
// open={selection.visible}
// onOpenChange={(visible) => {
// if (!visible) setSelection(prev => ({ ...prev, visible: false }));
// }}
// trigger="click"
// placement="bottom"
// // overlayStyle={{
// // position: 'fixed',
// // left: `${selection.position.x}px`,
// // top: `${selection.position.y}px`,
// // transform: 'translateX(-50%)'
// // }}
// // overlayInnerStyle={{ padding: '8px' }}
// >
// <div>
// {content}
// </div>
// </Popover>
// );
// };
// export default SelectableParagraph;

View File

@ -24,12 +24,25 @@
"title": "سورة",
"description": "هذه هي السورة رقم {{number}} من القرآن الكريم. تحتوي على {{verses}} آيات.",
"meta-verses": "آيات",
"meta-verse": "آيات",
"meta-selected-verse": "الآية المختارة",
"meta-tafsir": "التفسير",
"meta-words": "عدد الكلمات",
"meta-letters": "عدد الحروف",
"info": "معلومات عن السورة",
"name": "اسم السورة بالعربي",
"meaning": "اسم السورة بالإنجليزي",
"revealed": "مكان النزول"
},
"common": {
"loading": "جار التحميل...",
"error": "حدث خطأ. يرجى المحاولة مرة أخرى لاحقًا.",
"no_data": "لا توجد بيانات متاحة.",
"close": "إغلاق",
"select_language": "اختر اللغة",
"search": "بحث",
"submit": "إرسال",
"cancel": "إلغاء"
}
}
}

View File

@ -22,12 +22,25 @@
"title": "Surah",
"description": "This is Surah number {{number}} of the Holy Quran. It contains {{verses}} verses.",
"meta-verses": "Verses",
"meta-verse": "Verses",
"meta-selected-verse": "Selected Verse",
"meta-tafsir": "Tafsir",
"meta-words": "Word Count",
"meta-letters": "Letter Count",
"info": "Surah Details",
"name": "Surah Name",
"meaning": "Meaning",
"revealed": "Revelation Place"
},
"common":{
"loading": "Loading...",
"error": "An error occurred. Please try again later.",
"no_data": "No data available.",
"close": "Close",
"select_language": "Select Language",
"search": "Search",
"submit": "Submit",
"cancel": "Cancel"
}
}
}

View File

@ -9,10 +9,21 @@ import convertToArabicNumerals, { getNumberConversion } from '../../utils/conver
import { useTranslation } from 'react-i18next';
import { getSurahName, getSurahRevealationPlace } from '@/utils/surahUtil';
import { useLanguage } from '@/utils/LanguageProvider';
import SelectableParagraph from '@/components/SelectableParagraph';
import { Button, Modal } from 'antd';
import axios from 'axios';
export default function SurahPage({ surah, prevSurah, nextSurah }) {
const SurahPage = ({ surah, prevSurah, nextSurah }) => {
const { t } = useTranslation();
const { language } = useLanguage();
const [isModalOpen, setIsModalOpen] = React.useState(false);
const [selectedVerse, setSelectedVerse] = React.useState({
surahNo: surah ? surah.number : null,
verseNo: null,
text: "",
detail: null
});
const [loading, setLoading] = React.useState(false);
if (!surah) {
return (
@ -48,6 +59,31 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
}
};
const onDetailClick = async (detail) => {
// setSelectedVerse(detail);
setIsModalOpen(true);
try {
setLoading(true);
const res = await axios.post(process.env.NEXT_PUBLIC_API_URL + '/tafsir', {
verse_key: `${detail.surahNo}:${detail.verseNo}`,
verse_text: detail.selection
});
console.log('Tafsir response:', res.data);
const verseDetail = {
surahNo: detail.surahNo,
verseNo: detail.verseNo,
text: detail.selection,
detail: res.data
};
setSelectedVerse(verseDetail);
setLoading(false);
} catch (error) {
console.error('Error during action:', error);
return;
}
};
return (
<>
<SeoHead
@ -93,7 +129,7 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
</span>
</div>
<div className={styles.surahText}>
<div className={styles.surahText}>
{prefaceText && (
<p className={styles.bismillah}>{prefaceText}</p>
)}
@ -110,7 +146,11 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
return (
<div key={index} className={styles.verseContainer}>
<div className={styles.verseBox}>
<p className={styles.verseText} title={verse.text.ar} aria-label={verse.text.ar.split(" ").join("_")}>{verse.text.ar}</p>
<SelectableParagraph surahNo={surah.number} verseNo={index + 1} key={`p_${index}`}
index={index}
onDetailClick={onDetailClick}
content={<p className={styles.verseText} title={verse.text.ar} aria-label={verse.text.ar.split(" ").join("_")}>{verse.text.ar}</p>}>
</SelectableParagraph>
<p className={styles.verseTextEn} title={verse.text.en} aria-label={verse.text.en.split(" ").join("_")}>{verse.text.en}</p>
</div>
<span className={styles.VerseNumber} title={index + 1} aria-label={index + 1}>
@ -131,6 +171,27 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
})}
</div>
</main>
<Modal
title={<p>{`${getSurahName(surah, language)}`}</p>}
footer={
<Button type="primary" onClick={() => setIsModalOpen(false)}>
{t('common.close')}
</Button>
}
direction={language === 'ar' ? 'rtl' : 'ltr'}
loading={loading}
open={isModalOpen}
onCancel={() => setIsModalOpen(false)}
>
<div>
<h1 className={styles.title}>{t('surah.title')} {getSurahName(surah, language)}</h1>
<div>
<p className={styles.details}><b>{t('surah.meta-verse')}</b>: <span>{selectedVerse.verseNo && getNumberConversion(selectedVerse.verseNo, language)}</span></p>
<p className={styles.details}><b>{t('surah.meta-selected-verse')}</b>: <span>{selectedVerse.text && selectedVerse.text}</span></p>
<p className={styles.details}><b>{t('surah.meta-tafsir')}</b>: <span>{selectedVerse.detail && selectedVerse.detail.tafsir}</span></p>
</div>
</div>
</Modal>
</>
);
}
@ -181,4 +242,6 @@ export async function getStaticProps({ params }) {
},
};
}
}
}
export default SurahPage;

View File

@ -27,6 +27,10 @@
}
.details {
overflow-y: hidden;
scroll-behavior: smooth;
max-height: 200px;
background-color: var(--box-color);
padding-left: 20px;
padding-right: 20px;