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.
|
||||
* @returns
|
||||
*/
|
||||
transpilePackages: [
|
||||
'rc-util',
|
||||
"rc-picker",
|
||||
"rc-pagination",
|
||||
"@ant-design/icons-svg"
|
||||
],
|
||||
async redirects() {
|
||||
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",
|
||||
"dependencies": {
|
||||
"antd": "^5.26.1",
|
||||
"axios": "^1.10.0",
|
||||
"i18next": "^24.2.3",
|
||||
"next": "14.2.21",
|
||||
"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": "سورة",
|
||||
"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": "إلغاء"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user