Tafseer added with llm model
This commit is contained in:
parent
a08022ff5b
commit
ce20960d77
@ -17,6 +17,12 @@ const nextConfig = {
|
|||||||
* permanent: تعيينه على true يعني أن إعادة التوجيه دائمة (ستستخدم رمز الحالة 308). إذا كنت تريد أن تكون إعادة التوجيه مؤقتة، يمكنك تعيينه إلى false، وستستخدم رمز الحالة 307.
|
* permanent: تعيينه على true يعني أن إعادة التوجيه دائمة (ستستخدم رمز الحالة 308). إذا كنت تريد أن تكون إعادة التوجيه مؤقتة، يمكنك تعيينه إلى false، وستستخدم رمز الحالة 307.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
transpilePackages: [
|
||||||
|
'rc-util',
|
||||||
|
"rc-picker",
|
||||||
|
"rc-pagination",
|
||||||
|
"@ant-design/icons-svg"
|
||||||
|
],
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
4657
package-lock.json
generated
4657
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,8 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"antd": "^5.26.1",
|
||||||
|
"axios": "^1.10.0",
|
||||||
"i18next": "^24.2.3",
|
"i18next": "^24.2.3",
|
||||||
"next": "14.2.21",
|
"next": "14.2.21",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
|||||||
188
src/components/SelectableParagraph.jsx
Normal file
188
src/components/SelectableParagraph.jsx
Normal 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;
|
||||||
@ -24,12 +24,25 @@
|
|||||||
"title": "سورة",
|
"title": "سورة",
|
||||||
"description": "هذه هي السورة رقم {{number}} من القرآن الكريم. تحتوي على {{verses}} آيات.",
|
"description": "هذه هي السورة رقم {{number}} من القرآن الكريم. تحتوي على {{verses}} آيات.",
|
||||||
"meta-verses": "آيات",
|
"meta-verses": "آيات",
|
||||||
|
"meta-verse": "آيات",
|
||||||
|
"meta-selected-verse": "الآية المختارة",
|
||||||
|
"meta-tafsir": "التفسير",
|
||||||
"meta-words": "عدد الكلمات",
|
"meta-words": "عدد الكلمات",
|
||||||
"meta-letters": "عدد الحروف",
|
"meta-letters": "عدد الحروف",
|
||||||
"info": "معلومات عن السورة",
|
"info": "معلومات عن السورة",
|
||||||
"name": "اسم السورة بالعربي",
|
"name": "اسم السورة بالعربي",
|
||||||
"meaning": "اسم السورة بالإنجليزي",
|
"meaning": "اسم السورة بالإنجليزي",
|
||||||
"revealed": "مكان النزول"
|
"revealed": "مكان النزول"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "جار التحميل...",
|
||||||
|
"error": "حدث خطأ. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||||
|
"no_data": "لا توجد بيانات متاحة.",
|
||||||
|
"close": "إغلاق",
|
||||||
|
"select_language": "اختر اللغة",
|
||||||
|
"search": "بحث",
|
||||||
|
"submit": "إرسال",
|
||||||
|
"cancel": "إلغاء"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,12 +22,25 @@
|
|||||||
"title": "Surah",
|
"title": "Surah",
|
||||||
"description": "This is Surah number {{number}} of the Holy Quran. It contains {{verses}} verses.",
|
"description": "This is Surah number {{number}} of the Holy Quran. It contains {{verses}} verses.",
|
||||||
"meta-verses": "Verses",
|
"meta-verses": "Verses",
|
||||||
|
"meta-verse": "Verses",
|
||||||
|
"meta-selected-verse": "Selected Verse",
|
||||||
|
"meta-tafsir": "Tafsir",
|
||||||
"meta-words": "Word Count",
|
"meta-words": "Word Count",
|
||||||
"meta-letters": "Letter Count",
|
"meta-letters": "Letter Count",
|
||||||
"info": "Surah Details",
|
"info": "Surah Details",
|
||||||
"name": "Surah Name",
|
"name": "Surah Name",
|
||||||
"meaning": "Meaning",
|
"meaning": "Meaning",
|
||||||
"revealed": "Revelation Place"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9,10 +9,21 @@ import convertToArabicNumerals, { getNumberConversion } from '../../utils/conver
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getSurahName, getSurahRevealationPlace } from '@/utils/surahUtil';
|
import { getSurahName, getSurahRevealationPlace } from '@/utils/surahUtil';
|
||||||
import { useLanguage } from '@/utils/LanguageProvider';
|
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 { t } = useTranslation();
|
||||||
const { language } = useLanguage();
|
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) {
|
if (!surah) {
|
||||||
return (
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<SeoHead
|
<SeoHead
|
||||||
@ -93,7 +129,7 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.surahText}>
|
<div className={styles.surahText}>
|
||||||
{prefaceText && (
|
{prefaceText && (
|
||||||
<p className={styles.bismillah}>{prefaceText}</p>
|
<p className={styles.bismillah}>{prefaceText}</p>
|
||||||
)}
|
)}
|
||||||
@ -110,7 +146,11 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
|
|||||||
return (
|
return (
|
||||||
<div key={index} className={styles.verseContainer}>
|
<div key={index} className={styles.verseContainer}>
|
||||||
<div className={styles.verseBox}>
|
<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>
|
<p className={styles.verseTextEn} title={verse.text.en} aria-label={verse.text.en.split(" ").join("_")}>{verse.text.en}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.VerseNumber} title={index + 1} aria-label={index + 1}>
|
<span className={styles.VerseNumber} title={index + 1} aria-label={index + 1}>
|
||||||
@ -131,6 +171,27 @@ export default function SurahPage({ surah, prevSurah, nextSurah }) {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</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;
|
||||||
@ -27,6 +27,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
|
overflow-y: hidden;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
max-height: 200px;
|
||||||
|
|
||||||
background-color: var(--box-color);
|
background-color: var(--box-color);
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user