IntelliStudy PDF Companion Tool
IntelliStudy PDF Companion Tool
html ---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IntelliStudy PDF Companion</title>
<script src="[Link]
<script src="[Link]
<style>
@keyframes gradient-animation {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes aurora-animation {
0% { background-position: 0% 0%; }
50% { background-position: 100% 100%; }
100% { background-position: 0% 0%; }
}
@keyframes haze-animation {
0% { background-position: 0% 0%; }
100% { background-position: 100% 0%; }
}
@keyframes cyber-grid-animation {
0% { background-position: 0 0; }
100% { background-position: 0 -100px; }
}
@keyframes cyber-scanline-animation {
0% { transform: translateY(-10px); }
100% { transform: translateY(100vh); }
}
:root {
--bg-body: #111827;
--bg-main: rgba(17, 24, 39, 0.8);
--bg-alt: rgba(31, 41, 55, 0.8);
--bg-glass: rgba(31, 41, 55, 0.5);
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-accent: #60a5fa;
--border-color: rgba(55, 65, 81, 0.7);
--accent-primary: #3b82f6;
--accent-secondary: #10b981;
--accent-danger: #ef4444;
--accent-success: #22c55e;
--accent-warn: #f59e0b;
--shadow-color: rgba(0, 0, 0, 0.5);
--animatic-border: linear-gradient(120deg, #1e3a8a, #3b82f6, #60a5fa,
#3b82f6, #1e3a8a);
--border-animation: gradient-animation 15s ease infinite;
--status-green: var(--accent-success);
--status-yellow: var(--accent-warn);
--status-red: var(--accent-danger);
}
[Link]-space {
--bg-body: #0c0a1a;
--bg-main: rgba(20, 18, 41, 0.8);
--bg-alt: rgba(40, 36, 82, 0.8);
--bg-glass: rgba(40, 36, 82, 0.5);
--text-primary: #e0e0ff;
--text-secondary: #b0b0d0;
--text-accent: #a78bfa;
--border-color: rgba(76, 70, 138, 0.7);
--accent-primary: #8b5cf6;
--accent-secondary: #f472b6;
--animatic-border: linear-gradient(120deg, #4c1d95, #c026d3, #f472b6,
#7c3aed, #4c1d95);
--border-animation: gradient-animation 15s ease infinite;
--status-green: #22c55e;
--status-yellow: #f59e0b;
--status-red: #ef4444;
}
[Link]-dark {
--bg-body: #121212;
--bg-main: rgba(28, 28, 28, 0.8);
--bg-alt: rgba(44, 44, 44, 0.8);
--bg-glass: rgba(44, 44, 44, 0.5);
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--text-accent: #10b981;
--border-color: rgba(74, 74, 74, 0.7);
--accent-primary: #10b981;
--accent-secondary: #3b82f6;
--animatic-border: linear-gradient(-45deg, #059669, #10b981, #0ea5e9,
#3b82f6);
--border-animation: aurora-animation 20s ease infinite;
--status-green: #16a34a;
--status-yellow: #d97706;
--status-red: #dc2626;
}
[Link]-desert {
--bg-body: #281e16;
--bg-main: rgba(56, 40, 30, 0.8);
--bg-alt: rgba(87, 63, 48, 0.8);
--bg-glass: rgba(87, 63, 48, 0.5);
--text-primary: #fde68a;
--text-secondary: #fbbf24;
--text-accent: #f97316;
--border-color: rgba(120, 84, 62, 0.7);
--accent-primary: #f97316;
--accent-secondary: #ea580c;
--animatic-border: repeating-linear-gradient(45deg, #f59e0b, #f59e0b 10px,
#fbbf24 10px, #fbbf24 20px);
--border-animation: haze-animation 30s linear infinite;
--status-green: #65a30d;
--status-yellow: #f59e0b;
--status-red: #c2410c;
}
[Link]-nature {
--bg-body: #142117;
--bg-main: rgba(28, 51, 35, 0.8);
--bg-alt: rgba(44, 82, 54, 0.8);
--bg-glass: rgba(44, 82, 54, 0.5);
--text-primary: #d4d4d4;
--text-secondary: #a3a3a3;
--text-accent: #84cc16;
--border-color: rgba(62, 115, 76, 0.7);
--accent-primary: #84cc16;
--accent-secondary: #22c55e;
--animatic-border: linear-gradient(120deg, #166534, #22c55e, #a3e635,
#4d7c0f, #166534);
--border-animation: gradient-animation 20s ease infinite;
--status-green: #4d7c0f;
--status-yellow: #ca8a04;
--status-red: #be123c;
}
[Link]-aurora {
--bg-body: #010115;
--bg-main: rgba(10, 5, 30, 0.7);
--bg-alt: rgba(20, 15, 50, 0.7);
--bg-glass: rgba(20, 15, 50, 0.4);
--text-primary: #e6e6fa;
--text-secondary: #b0c4de;
--text-accent: #89fffd;
--border-color: rgba(60, 50, 120, 0.7);
--accent-primary: #ff79c6;
--accent-secondary: #50fa7b;
--accent-danger: #ff5555;
--accent-warn: #f1fa8c;
--shadow-color: rgba(0, 0, 0, 0.5);
--animatic-border: none;
--status-green: var(--accent-secondary);
--status-yellow: var(--accent-warn);
--status-red: var(--accent-danger);
}
[Link]-cyber {
--bg-body: #0d0d0d;
--bg-main: rgba(20, 20, 20, 0.75);
--bg-alt: rgba(30, 30, 30, 0.75);
--bg-glass: rgba(20, 20, 20, 0.5);
--text-primary: #00ffde;
--text-secondary: #a1a1a1;
--text-accent: #ff00ff;
--border-color: rgba(0, 255, 222, 0.3);
--accent-primary: #00ffde;
--accent-secondary: #ff00ff;
--accent-danger: #ff5555;
--accent-success: #50fa7b;
--accent-warn: #f1fa8c;
--shadow-color: rgba(0, 255, 222, 0.2);
--animatic-border: none;
--status-green: var(--accent-success);
--status-yellow: var(--accent-warn);
--status-red: var(--accent-danger);
}
body {
background-color: var(--bg-body);
color: var(--text-primary);
transition: background-color 0.5s ease;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
background: var(--animatic-border);
background-size: 400% 400%;
animation: var(--border-animation);
transition: background 0.5s ease;
}
[Link]-aurora::before {
background: radial-gradient(circle at 20% 20%, #89fffd20, transparent 40%),
radial-gradient(circle at 80% 70%, #ff79c620, transparent 40%),
radial-gradient(circle at 50% 50%, #50fa7b10, transparent 30%);
animation: aurora-animation 30s ease-in-out infinite;
}
[Link]-cyber::before {
background-image: linear-gradient(var(--accent-primary) 1px, transparent
1px), linear-gradient(to right, var(--accent-primary) 1px, var(--bg-body) 1px);
background-size: 50px 50px;
opacity: 0.1;
animation: cyber-grid-animation 3s linear infinite;
}
[Link]-cyber::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 5px;
background: linear-gradient(to bottom, rgba(17, 24, 39, 0) 0%, var(--
accent-primary) 75%, var(--accent-secondary) 100%);
opacity: 0.2;
z-index: -1;
animation: cyber-scanline-animation 8s linear infinite;
}
[Link]-cyber * {
text-shadow: 0 0 2px var(--shadow-color);
}
.[Link]-green { background-color: var(--status-green); }
.[Link]-yellow { background-color: var(--status-yellow); }
.[Link]-red { background-color: var(--status-red); }
function AppContent() {
const { viewMode } = useContext(ViewModeContext);
const [appMode, setAppMode] = useState<AppMode>(() =>
([Link]('appMode') as AppMode) || 'intellistudy');
const [documents, setDocuments] = useState<Document[]>(() => {
try {
const stored = [Link]('sessionDocuments');
return stored ? [Link](stored) : [];
} catch {
return [];
}
});
const [sessionName, setSessionName] = useState<string>(() =>
[Link]('sessionName') || '');
const [isProcessing, setIsProcessing] = useState(false);
useEffect(() => {
[Link]('appMode', appMode);
}, [appMode]);
useEffect(() => {
try {
[Link]('sessionDocuments', [Link](documents));
} catch (e) {
[Link]("Error saving documents to localStorage:", e);
}
}, [documents]);
useEffect(() => {
[Link]('sessionName', sessionName);
}, [sessionName]);
return (
<div className="flex flex-col h-full">
<header style={{ backgroundColor: 'var(--bg-glass)' }} className="flex-
shrink-0 backdrop-blur-sm border-b border-[var(--border-color)] p-2 flex justify-
between items-center">
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold text-[var(--text-
primary)]">IntelliStudy</h1>
<div className="flex items-center bg-[var(--bg-main)] rounded-full p-1
border border-[var(--border-color)]">
<button
onClick={() => setAppMode('intellistudy')}
className={`px-4 py-1 text-sm rounded-full transition-colors $
{appMode === 'intellistudy' ? 'bg-[var(--accent-primary)] text-white' : 'text-
[var(--text-secondary)] hover:text-[var(--text-primary)]'}`}
>
IntelliStudy
</button>
<button
onClick={() => setAppMode('notebook')}
className={`px-4 py-1 text-sm rounded-full transition-colors $
{appMode === 'notebook' ? 'bg-[var(--accent-primary)] text-white' : 'text-[var(--
text-secondary)] hover:text-[var(--text-primary)]'}`}
>
Notebook Assistant
</button>
</div>
</div>
<div className="flex items-center gap-2">
<ViewModeSwitcher />
<ThemeSwitcher />
<button
onClick={handleExitSession}
className="p-2 rounded-full bg-[var(--bg-main)] border border-[var(--
border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]
hover:border-[var(--accent-danger)] transition-colors"
aria-label="Exit session and start new"
title="Exit session and start new"
>
<ExitIcon />
</button>
</div>
</header>
return (
<div className={`h-screen w-screen bg-[var(--bg-main)] text-[var(--text-
primary)] font-sans overflow-hidden ${viewMode === 'mobile' ? 'view-mobile' :
''}`}>
{renderContent()}
</div>
);
}
function App() {
return (
<ThemeProvider>
<ViewModeProvider>
<AppContent />
</ViewModeProvider>
</ThemeProvider>
);
}
interface IntelliStudyAppProps {
combinedPages: string[];
sessionName: string;
}
if ([Link] === 0) {
return [{ title: `Full Document (Pages 1-${totalPages})`, startPage: 1,
endPage: totalPages }];
}
currentPage = [Link] + 1;
}
if ([Link] > 0) {
const lastLesson = sanitized[[Link] - 1];
if ([Link] > totalPages) {
[Link] = totalPages;
}
}
useEffect(() => {
if (view === 'study' && activeLesson) {
const updatedActiveLesson = [Link](l => [Link] === [Link]);
if (updatedActiveLesson) {
if ([Link](activeLesson) !== [Link](updatedActiveLesson)) {
setActiveLesson(updatedActiveLesson);
}
} else {
setActiveLesson(null);
setView('dashboard');
}
}
}, [lessonsData, view, activeLesson]);
useEffect(() => {
const initializeDocument = async () => {
if (isLoading) return;
setView('dashboard');
setActiveLesson(null);
}
};
initializeDocument();
}, [combinedPages, lessonsData, isLoading, setAllLessonsData]);
if (isCategorizing) {
return (
<div className="flex flex-col items-center justify-center h-full text-
center p-4">
<Loader />
<p className="mt-4 text-lg text-[var(--text-secondary)]">Analyzing
documents and creating lessons...</p>
<p className="text-sm text-[var(--text-secondary)]">This might take a
moment for large documents.</p>
</div>
);
}
interface ThemeContextType {
theme: ThemeName;
setTheme: (theme: ThemeName) => void;
}
useEffect(() => {
[Link] = `theme-${theme}`;
try {
[Link]('app-theme', theme);
} catch (error) {
[Link]("Could not save theme to localStorage", error)
}
}, [theme]);
return (
<[Link] value={value}>
{children}
</[Link]>
);
};
--- END OF FILE contexts/[Link] ---
interface ViewModeContextType {
viewMode: ViewMode;
setViewMode: (mode: ViewMode) => void;
}
useEffect(() => {
try {
const storedMode = [Link]('app-view-mode');
if (storedMode === 'mobile' || storedMode === 'desktop') {
setViewMode(storedMode as ViewMode);
}
} catch (error) {
[Link]("Could not load view mode from localStorage", error);
}
}, []);
return (
<[Link] value={value}>
{children}
</[Link]>
);
};
--- END OF FILE contexts/[Link] ---
try {
const storedMessages = [Link](messagesKey);
if (storedMessages) {
setMessagesInternal([Link](storedMessages));
} else {
setMessagesInternal([]);
}
interface AllQuizSessions {
[lessonId: string]: {
october6Exam?: QuizSession;
studyMcq?: QuizSession;
realExam?: QuizSession;
impossible?: QuizSession;
studyHighlights?: QuizSession;
egyptianExams?: QuizSession;
clinicalPearls?: QuizSession;
mockExam?: QuizSession;
efficientStudying?: QuizSession;
postGraduatePrep?: QuizSession;
postGraduateExam?: QuizSession;
}
}
if (daysSinceStudied > 7) {
return 'red';
}
if (daysSinceStudied > 1) {
return 'yellow';
}
return 'green';
};
useEffect(() => {
if (!fileName) {
setIsLoading(false);
return;
}
setIsLoading(true);
try {
const storedData = [Link](lessonsDataKey);
const storedGoal = [Link]('dailyGoal');
if (storedData) {
const parsedData: Lesson[] = [Link](storedData);
const updatedData = [Link](lesson => ({
...lesson,
status: getStatus([Link])
}));
setLessonsData(updatedData);
} else {
setLessonsData([]);
}
if (storedGoal) {
setDailyGoal([Link](storedGoal));
}
} catch (error) {
[Link]("Failed to load data from localStorage", error);
[Link](lessonsDataKey);
[Link]('dailyGoal');
} finally {
setIsLoading(false);
}
}, [fileName, lessonsDataKey]);
useEffect(() => {
if (!fileName) return;
try {
const stored = [Link](userExamsKey);
if (stored) {
setUserExams([Link](stored));
} else {
setUserExams([]);
}
} catch (error) {
[Link]("Failed to load user exams from localStorage", error);
}
}, [fileName, userExamsKey]);
return text;
};
return [Link](cleanedString) as T;
} catch (e) {
[Link]("Failed to parse JSON:", e);
[Link]("Raw string:", jsonString);
try {
let fixedJson = cleanAndExtractJson(jsonString);
fixedJson = [Link](/,\s*([}\]])/g, '$1');
const parsed = [Link](fixedJson) as T;
[Link]("Successfully parsed after fixing JSON string.");
return parsed;
} catch (e2) {
[Link]("Failed to parse JSON even after fixing:", e2);
[Link]("Problematic JSON string:", jsonString);
return null;
}
}
};
return [Link];
} catch (error) {
[Link]("Error extracting text from image:", error);
throw new Error("Could not extract text from the image. Please ensure the
image is clear and try again.");
}
};
TEXT TO FORMAT:
---
${text}
---`,
});
return [Link];
} catch (error) {
[Link]("Error formatting study text:", error);
return [Link](/\n/g, '<br/>');
}
};
RULES:
- For "KEY_CONCEPTS" objects, include a "concept" (string) and "details" (array of
strings).
- For "TABLE" objects, include a "title" (string), "headers" (array of strings),
and "rows" (array of arrays of strings).
- The "title" for a table MUST BE a very short, descriptive string, strictly under
15 words.
- CRITICAL: The "title" field MUST NOT contain any legal disclaimers, medical
advice, warnings, or any text other than a brief description of the table's
content.
- Do not include any text, explanations, or markdown formatting like \`\`\`json
outside of the JSON object.
TEXT TO ANALYZE:
---
${text}
---`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
content: {
type: [Link],
items: {
type: [Link],
properties: {
type: {
type: [Link],
enum: [ContentBlockType.KEY_CONCEPTS,
[Link]],
},
concept: { type: [Link] },
details: {
type: [Link],
items: { type: [Link] },
},
title: { type: [Link] },
headers: {
type: [Link],
items: { type: [Link] },
},
rows: {
type: [Link],
items: { type: [Link], items: { type:
[Link] } },
},
},
required: ["type"]
}
}
},
required: ["content"]
}
}
});
if (!parsed || ) {
[Link]("Parsed content is not valid:", parsed);
return { content: [] };
}
return parsed;
} catch (error) {
[Link]("Error generating enhanced study content:", error);
return { content: [] };
}
};
if (totalPages > 0) {
if (totalPages <= MAX_SAMPLES) {
for (let i = 0; i < totalPages; i++) {
[Link](`--- Page ${i + 1} Start ---\n$
{pagesText[i].substring(0, 300)}...`);
}
} else {
const step = [Link](totalPages / MAX_SAMPLES);
for (let i = 0; i < totalPages; i += step) {
[Link](`--- Page ${i + 1} Start ---\n$
{pagesText[i].substring(0, 300)}...`);
}
if ((totalPages - 1) % step !== 0) {
[Link](`--- Page ${totalPages} Start ---\n$
{pagesText[totalPages - 1].substring(0, 300)}...`);
}
}
}
DOCUMENT SUMMARY:
---
${conciseText}
---
RULES:
1. **Understand the Source:** The document summary is compiled from
one or more source files. The text for each page may start with a "[Source:
filename]" tag. Use this as a strong hint for topic changes, but you should still
group pages from different sources if the topic is clearly continuous (e.g., a
chapter in a book and its corresponding lecture slides).
2. **Find the Right Balance:** Your primary goal is to find a balance
between creating overly broad lessons that cover too many disparate topics and
creating too many small, fragmented lessons.
3. **Coherent Grouping:** Group pages that cover a single, coherent
topic into one lesson.
4. **Logical Lesson Size:** Aim for lessons that are typically between
3 and 15 pages long.
5. **Generate Specific Titles:** For each lesson, create a title that
is a concise and accurate summary of its content.
6. **Ensure Full Coverage:** Page ranges must be contiguous and cover
all pages from 1 to ${[Link]}. If lesson 1 ends at page 5, lesson 2 must
start at page 6. The final lesson must end on page ${[Link]}.
7. **Output Format:** Return a valid JSON object with a single root
key "lessons", which is an array of lesson objects. Each lesson object must have
"title" (string), "startPage" (number), and "endPage" (number).
8. **Output ONLY the JSON object.** No extra text or markdown.
if (!parsed || ) {
[Link]("Failed to parse lesson categories:", parsed);
return [];
}
if ([Link] > 0) {
const lastLesson = [Link][[Link] - 1];
if ([Link] !== [Link]) {
[Link](`Lesson categorization might be incomplete. Expected
last page ${[Link]}, but got ${[Link]}. Adjusting...`);
[Link] = [Link];
}
}
return [Link];
} catch (error) {
[Link]("Error generating lesson categories:", error);
return [];
}
};
RULES:
1. Generate 3-5 questions.
2. For each question, provide: "question", "options" (an array of 4
strings), "correctAnswer" (a string matching one of the options), "source" (string
indicating which specific past exam it's modeled after, e.g., 'October 6 University
- Final Year Cardiology Exam 2023'), and "tags" (an array of strings like "O6U-
Style", "High-Yield").
3. CRITICAL: The "explanation" must be in **Markdown format**. It
should be simple, direct, and clear, making it easy for a student to understand.
Use bolding for key terms. It must briefly explain why the correct answer is right
and why the other options are wrong.
4. Your questions MUST synthesize information from BOTH the 'MAIN
TEXT' and the 'HIGH-YIELD INFORMATION' when relevant. Prioritize testing concepts
from the high-yield info.
5. DO NOT repeat any questions from the provided history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${text}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid exam questions. The AI's
response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating exam questions:", error);
throw new Error("Could not generate exam questions. Please try again
later.");
}
};
RULES:
1. Generate 3-5 non-case-based questions.
2. For each question, provide: "question", "options" (an array of 4
strings), "correctAnswer" (a string matching one of the options), "source" (string
indicating the university and year, e.g., 'Cairo University - 2022'), and "tags"
(an array of strings like "Direct Recall", "Egyptian Exam").
3. CRITICAL: The "explanation" must be in **Markdown format**. It
should be simple, direct, and clear, making it easy for a student to understand.
Use bolding for key terms. It must briefly explain why the correct answer is right
and why the other options are wrong.
4. Your questions MUST synthesize information from BOTH the 'MAIN
TEXT' and the 'HIGH-YIELD INFORMATION' when relevant. Prioritize testing concepts
from the high-yield info.
5. DO NOT repeat any questions from the provided history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${text}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid Egyptian exam questions. The
AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating Egyptian exam questions:", error);
throw new Error("Could not generate Egyptian exam questions. Please try
again later.");
}
};
RULES:
1. Scrutinize the text and create a question for every single fact,
definition, concept, or key detail. Do not skip any information.
2. The number of questions should not be fixed. Generate as many
questions as necessary to cover the entire text exhaustively.
3. For each question, provide: "question", "options" (an array of 4
strings), "correctAnswer" (a string matching one of the options), and a detailed
"explanation" that reinforces the learning point.
4. Frame questions to test direct recall of the information as
presented in the text.
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
},
required: ["question", "options", "correctAnswer",
"explanation"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid study questions. The AI's
response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating study questions:", error);
throw new Error("Could not generate study questions. Please try again
later.");
}
};
export const generateStudyHighlightsMcqs = async (text: string, highYieldText:
string | undefined, history: PracticeQuestion[] = []): Promise<PracticeQuestion[]>
=> {
try {
const response = await [Link]({
model: "gemini-2.5-flash",
contents: `Act as a study assistant. Your goal is to help a user learn
the main points from the provided text by creating multiple-choice questions.
RULES:
1. Generate 5-10 multiple-choice questions that cover the most
important highlights, key concepts, and main ideas from the text.
2. Prioritize creating questions from the "HIGH-YIELD INFORMATION"
first, then supplement with key concepts from the "MAIN TEXT".
3. For each question, provide: "question", "options" (an array of 4
strings), "correctAnswer" (a string matching one of the options), and "tags" (an
array of strings like "Highlight", "Key Concept").
4. CRITICAL: The "explanation" must be in **Markdown format**. It must
be simple, direct, and clear, making it easy for a student to understand. Use
bolding for key terms.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${text}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid study highlight questions.
The AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating study highlight questions:", error);
throw new Error("Could not generate study highlight questions. Please try
again later.");
}
};
RULES:
1. Generate 3-5 questions.
2. For each question, provide: "question", "options" (an array of 4
strings), "correctAnswer" (a string matching one of the options), "source" (string
indicating which past exam style it's modeled after, e.g., 'Cardiology Exam 2022'),
and "tags" (an array of strings like "Case-based", "High-Yield").
3. CRITICAL: The "explanation" must be in **Markdown format**. It
should be simple, direct, and clear, making it easy for a student to understand.
Use bolding for key terms.
4. Your questions MUST synthesize information from BOTH the 'MAIN
TEXT' and the 'HIGH-YIELD INFORMATION' when relevant. Prioritize testing concepts
from the high-yield info.
5. DO NOT repeat any questions from the provided history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${text}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid real exam questions. The
AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating real exam questions:", error);
throw new Error("Could not generate real exam questions. Please try again
later.");
}
};
RULES:
1. Generate 3-5 questions.
2. Include a mix of question styles: some requiring deep conceptual
understanding, some tricky questions with subtle distractors, and some standard-
difficulty questions to balance the set.
3. For each question, provide: "question", "options" (an array of 4-5
strings), "correctAnswer", a "source" (string indicating the exam style, e.g.,
'Cairo University Final Exam 2021'), and "tags" (an array of strings like "Tricky",
"Multi-step", "Impossible").
4. CRITICAL: The "explanation" must be very detailed but clear, in
**Markdown format**. It should thoroughly break down the complex reasoning,
explaining not just the correct answer but why the subtle distractors are
incorrect. Use bolding for key terms.
5. Your questions MUST synthesize information from BOTH the 'MAIN
TEXT' and the 'HIGH-YIELD INFORMATION' when relevant. Prioritize testing concepts
from the high-yield info.
6. DO NOT repeat any questions from the provided history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${text}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid impossible questions. The
AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating impossible questions:", error);
throw new Error("Could not generate impossible questions. Please try again
later.");
}
};
if ([Link] === 0) {
return [];
}
RULES:
1. You MUST generate exactly ${[Link]} questions. No
more, no less.
2. The questions should be ${questionStyle}.
3. For each question, provide: "question", "options" (4 strings),
"correctAnswer", a "source" (e.g., 'Mock Exam - Case-Based'), "sourceLessonId"
(matching the ID from the lesson marker), and "tags" (an array of strings
indicating the question type, e.g., "Case-Based", "Foundational", "Clinical Sign").
4. CRITICAL: The "explanation" must be in **Markdown format**. For
questions based on the text, it must cite evidence from BOTH the lesson text and
high-yield info where applicable. For general knowledge questions, it should
provide a concise, accurate clinical explanation. Use bolding for key terms.
5. **Avoid Repetition:** DO NOT generate questions that are the same
as or very similar to the ones provided in the 'PRACTICE HISTORY'. CRITICAL: You
must ensure the new questions are substantively different. Do not just rephrase old
questions.
KNOWLEDGE SOURCES:
---
LESSON TEXT:
${lessonText}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
PRACTICE HISTORY (questions to avoid):
---
${[Link] > 0 ? [Link]([Link](q => [Link])) :
"None"}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
sourceLessonId: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "sourceLessonId", "tags"]
}
}
},
required: ["questions"]
}
}
});
const jsonStr = [Link]();
const parsed = parseJsonGracefully<{ questions:
PracticeQuestion[] }>(jsonStr);
if (!parsed || ) {
throw new Error("Failed to generate valid mock exam questions. The
AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating mock exam questions:", error);
throw new Error("Could not generate mock exam questions. Please try again
later.");
}
};
Keep the feedback concise and focused on helping the user improve their
memory of the material.
ORIGINAL TEXT:
---
${originalText}
---
RULES:
1. **Go Beyond the Text:** Your most important task is to include
crucial, high-yield facts, concepts, mnemonics, or clinical pearls that are related
to the lesson's topic but may NOT be explicitly mentioned in the provided text.
Think about what a professor would emphasize in a review session.
2. **Summarize Key Points:** Briefly summarize the absolute must-know
points from the provided text.
3. **Format for Learning:** Use Markdown for clear formatting. Use
headings, bold text, bullet points (especially for lists of symptoms, treatments,
etc.), and even tables if it helps organize information.
4. **Be Concise:** Keep the information dense and to the point. Avoid
long paragraphs.
5. **Return Only Markdown:** Do not include any introductory or
concluding sentences like "Here is the high-yield information...". Just return the
formatted Markdown content.
RULES:
1. Generate 3-5 questions.
2. For each question, provide: "question", "options" (an array of 4
strings), "correctAnswer", a "source" (string, which should be 'Clinical Pearl'),
and "tags" (an array of strings like "Clinical Pearl", "Anatomy", "Pro-Tip").
3. CRITICAL: The "explanation" must be in **Markdown format**. It
should be simple, direct, and clear, making it easy for a student to understand,
referencing the concepts from the provided Clinical Pearls. Use bolding for key
terms.
4. DO NOT repeat any questions from the provided history.
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid clinical pearls questions.
The AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating clinical pearls questions:", error);
throw new Error("Could not generate clinical pearls questions. Please try
again later.");
}
};
YOUR TASK:
Generate a balanced set of 10-15 multiple-choice questions that
intelligently synthesizes information from ALL THREE sources. This is not just a
random collection; it is a curated set designed for maximum learning efficiency.
RULES:
1. **Comprehensive Coverage:** Your questions MUST cover key concepts
from the Main Text, facts from the High-Yield Info, AND the practical wisdom from
the Clinical Pearls.
2. **Balanced Mix:** Create a mix of question types. Include direct
recall questions, questions that require synthesis of ideas, and questions testing
the "assumed" knowledge found in the Clinical Pearls.
3. **Prioritize Integration:** Create some questions that require the
student to connect a concept from the Main Text with a detail from the High-Yield
Info or a Clinical Pearl.
4. **For each question, provide:** "question", "options" (4 strings),
"correctAnswer", a "source" ('Efficient Study Mix'), and "tags" (an array
accurately describing the question's focus, e.g., "High-Yield", "Clinical Pearl",
"Core Concept", "Anatomy").
5. **CRITICAL:** The "explanation" for each question must be in
**Markdown format**. It must be exceptionally clear, concise, and use bolding for
key terms to aid memory.
6. **Avoid Repetition:** DO NOT repeat questions from the provided
history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${lessonText}
---
HIGH-YIELD INFO:
${highYieldInfo}
---
CLINICAL PEARLS:
${clinicalPearls}
---
Return the result as a JSON object with a single root key "questions"
which is an array of question objects.`,
config: {
responseMimeType: "application/json",
responseSchema: {
type: [Link],
properties: {
questions: {
type: [Link],
items: {
type: [Link],
properties: {
question: { type: [Link] },
options: { type: [Link], items: { type:
[Link] } },
correctAnswer: { type: [Link] },
explanation: { type: [Link] },
source: { type: [Link] },
tags: { type: [Link], items: { type:
[Link] } },
},
required: ["question", "options", "correctAnswer",
"explanation", "source", "tags"]
}
}
},
required: ["questions"]
}
}
});
if (!parsed || ) {
throw new Error("Failed to generate valid efficient study questions.
The AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating efficient study questions:", error);
throw new Error("Could not generate efficient study questions. Please try
again later.");
}
};
RULES:
1. **Generate 3-5 questions.**
2. **Create Realistic Cases:** Each question should present a clinical
vignette or scenario that requires multi-step reasoning.
3. **For each question, provide:** "question", "options" (4 strings),
"correctAnswer", a "source" ('Post-Graduate Prep'), and "tags" (e.g., "Case-Based",
"Clinical Reasoning", "PG-Prep").
4. **CRITICAL - Tutorial-Style Explanation:** The "explanation" is the
most important part. It MUST be a detailed, step-by-step tutorial in **Markdown
format**. It should:
* Clearly break down the clinical reasoning process.
* Explain why the correct answer is the best choice, citing
evidence from the provided text.
* Thoroughly analyze why EACH of the other options is incorrect.
* Use bolding, bullet points, and clear headings to make it an
effective learning tool.
5. **Synthesize Knowledge:** Questions should require synthesizing
information from both the 'MAIN TEXT' and the 'HIGH-YIELD INFORMATION'.
6. **Avoid Repetition:** Do not repeat questions from the provided
history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${lessonText}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
} catch (error) {
[Link]("Error generating Post-Graduate Prep questions:", error);
throw new Error("Could not generate Post-Graduate Prep questions. Please
try again later.");
}
};
RULES:
1. **Generate 3-5 questions.**
2. **Style:** Questions can be case-based or direct, but must be
challenging and reflect the complexity of post-graduate exams. Focus on subtle
distinctions and high-yield facts.
3. **For each question, provide:** "question", "options" (4 strings),
"correctAnswer", a "source" ('Post-Graduate Exam'), and "tags" (e.g., "Board-
Style", "High-Yield", "PG-Exam").
4. **CRITICAL - Concise Explanation:** The "explanation" must be in
**Markdown format**, but it should be concise, direct, and to the point. Use
bolding for key terms. This is for rapid-fire review, not a deep-dive tutorial.
5. **Synthesize Knowledge:** Questions should require synthesizing
information from both the 'MAIN TEXT' and the 'HIGH-YIELD INFORMATION'.
6. **Avoid Repetition:** Do not repeat questions from the provided
history.
KNOWLEDGE SOURCES:
---
MAIN TEXT:
${lessonText}
---
HIGH-YIELD INFORMATION:
${highYieldText || 'No supplementary high-yield information was
provided for this topic.'}
---
} catch (error) {
[Link]("Error generating Post-Graduate Exam questions:", error);
throw new Error("Could not generate Post-Graduate Exam questions. Please
try again later.");
}
};
RULES:
1. Base your answer *exclusively* on the provided text. Do not use
outside knowledge.
2. If the answer cannot be found in the text, state that clearly.
3. Identify the source and page numbers that support your answer.
4. Return a JSON object with two keys: "answer" (string) and
"citations" (an array of objects).
5. Each citation object must have a "source" (string, the filename)
and a "page" (number).
SOURCE DOCUMENT:
---
${documentText}
---
if (!parsed) {
throw new Error("The AI's response was not in the expected JSON
format.");
}
return parsed;
} catch (error) {
[Link]("Error generating grounded response:", error);
throw new Error("Could not generate a response. Please try again.");
}
};
RULES:
1. Scan the entire text to find questions and answers.
2. The correct answer might be indicated by an asterisk (*), bolding,
underlining, or listed in an answer key (often at the end of the document).
Prioritize finding an explicitly marked answer.
3. If you cannot determine the correct answer from any explicit
marking or answer key, use the context of the entire document to infer the most
likely correct answer.
4. For each question, provide: "question", "options" (array of 4-5
strings), "correctAnswer" (a string exactly matching one of the options), and
"explanation" formatted in **Markdown**. Also include "tags" as an empty array.
5. The output must be a valid JSON object with a single root key
"questions", which is an array of question objects. Do not include any other text
or markdown.
if (!parsed || ) {
throw new Error("Failed to generate valid questions from the exam
file. The AI's response was not in the expected format.");
}
return [Link];
} catch (error) {
[Link]("Error generating questions from exam file:", error);
throw new Error("Could not process the exam file. Please ensure it is a
text-based document and try again.");
}
};
--- END OF FILE services/[Link] ---
interface DashboardProps {
combinedPages: string[];
sessionName: string;
lessonsData: Lesson[];
onSelectLesson: (lessonId: string) => void;
dailyGoal: number;
setDailyGoal: (goal: number) => void;
lessonsStudiedToday: number;
updateLesson: (lessonId: string, updates: Partial<Lesson>) => void;
addQuestionsToHistory: (lessonId: string, questions: PracticeQuestion[], type:
QuizType) => void;
userExams: UserExam[];
addUserExams: (exams: UserExam[]) => void;
deleteUserExam: (examId: string) => void;
}
// Review State
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
const [reviewModalTitle, setReviewModalTitle] = useState('');
const [questionsToReview, setQuestionsToReview] =
useState<PracticeQuestion[]>([]);
const allMistakes = useMemo(() => {
return [Link](lesson => {
const histories = [
lesson.october6ExamHistory,
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
];
return [Link](history => history?.filter(q => [Link] ===
false) || []);
});
}, [lessonsData]);
useEffect(() => {
try {
[Link](mockExamLessonsKey,
[Link]([Link](mockExamLessons)));
} catch (error) {
[Link]("Failed to save mock exam lessons selection:", error);
}
}, [mockExamLessons, mockExamLessonsKey]);
const lessonsByStatus = {
green: [Link](p => [Link] === 'green').length,
yellow: [Link](p => [Link] === 'yellow').length,
red: [Link](p => [Link] === 'red').length,
};
[Link](key => {
const value = [Link](key);
if (value !== null) {
try {
data[key] = [Link](value);
} catch (e) {
data[key] = value;
}
}
});
setMockExamQuestions(questions);
setIsMockExamModalOpen(true);
} catch (err) {
setPgPrepError(err instanceof Error ? [Link] : "An unknown error
occurred.");
} finally {
setIsGeneratingPgPrep(false);
}
};
setMockExamQuestions(questions);
setIsMockExamModalOpen(true);
} catch (err) {
setPgExamError(err instanceof Error ? [Link] : "An unknown error
occurred.");
} finally {
setIsGeneratingPgExam(false);
}
};
setIsMockExamModalOpen(false);
};
return (
<div className="p-4 sm:p-6 lg:p-8">
{isMockExamModalOpen && <MockExamModal title="Generated Exam"
questions={mockExamQuestions} onClose={() => setIsMockExamModalOpen(false)}
onComplete={handleMockExamComplete} />}
{isReviewModalOpen && <ReviewMistakesModal title={reviewModalTitle}
questions={questionsToReview} onClose={() => setIsReviewModalOpen(false)} />}
{activeUserExam && <UserExamModal exam={activeUserExam} onClose={() =>
setActiveUserExam(null)} />}
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold
text-[var(--text-primary)]">Dashboard</h1>
<button
onClick={handleExportData}
className="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-
[var(--text-secondary)] bg-[var(--bg-alt)] border border-[var(--border-color)]
rounded-lg hover:text-[var(--text-primary)] hover:border-[var(--accent-primary)]
transition-colors"
>
<DownloadIcon />
Export Progress
</button>
</div>
<div className="mb-8">
<h2 className="text-2xl font-bold mb-4 text-[var(--text-primary)]">Exam
Practice Center</h2>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-1">
<ExamUploader onExamsProcessed={addUserExams} />
</div>
<div className="lg:col-span-2 bg-[var(--bg-alt)] p-4 rounded-lg border
border-[var(--border-color)]">
<h3 className="text-lg font-bold mb-3 text-[var(--text-
primary)]">My Custom Exams</h3>
{[Link] > 0 ? (
<div className="space-y-3 max-h-60 overflow-y-auto">
{[Link](exam => (
<div key={[Link]} className="bg-[var(--bg-main)] p-3
rounded-lg flex items-center justify-between">
<div className="flex items-center gap-3">
<FileTextIcon className="text-[var(--text-
accent)]"/>
<div>
<p className="font-semibold text-sm text-
[var(--text-primary)]">{[Link]}</p>
<p className="text-xs text-[var(--text-
secondary)]">{[Link]} questions</p>
</div>
</div>
<div className="flex items-center gap-2">
<button onClick={() => setActiveUserExam(exam)}
className="px-4 py-1.5 text-sm bg-[var(--accent-secondary)] text-white font-bold
rounded-md hover:opacity-90 transition-opacity">
Start
</button>
<button onClick={() => deleteUserExam([Link])}
className="p-2 text-[var(--accent-danger)] hover:bg-[var(--accent-danger)]/20
rounded-md transition-colors">
<Trash2Icon />
</button>
</div>
</div>
))}
</div>
) : (
<p className="text-center text-sm text-[var(--text-secondary)]
py-8">Upload an exam file to get started.</p>
)}
</div>
</div>
</div>
<div>
<h2 className="text-2xl font-bold mb-4 text-[var(--text-
primary)]">Lessons</h2>
{lessonsData && [Link] > 0 ? (
<div className="space-y-4">
{[Link](lesson => {
const isConquered = CONQUEST_STEP_IDS.every(stepId =>
[Link]?.includes(stepId));
return (
<div key={[Link]} className={`bg-[var(--bg-alt)] rounded-lg
border-l-4 transition-all duration-300`}
style={{ borderColor: statusConfig[[Link]].color }}
>
<button
onClick={() => onSelectLesson([Link])}
className="w-full p-4 flex justify-between items-center text-
left"
aria-label={`Study lesson: ${[Link]}`}
>
<div className="flex-1 pr-4">
<div className="flex items-center gap-3">
<h3 className="text-lg font-bold text-[var(--text-
primary)]">{[Link]}</h3>
{isConquered && (
<span className="px-2 py-0.5 text-xs font-bold
text-yellow-300 bg-yellow-500/20 border border-yellow-500/50 rounded-full">
CONQUERED
</span>
)}
</div>
<p className="text-sm text-[var(--text-secondary)]">Pages
{[Link]} - {[Link]}</p>
</div>
<div className="flex items-center gap-4 flex-shrink-0">
<div className="flex items-center gap-2">
<div className="w-5 h-5" style={{ color:
statusConfig[[Link]].color }}>{[Link](statusConfig[[Link]
s].icon)}</div>
<span className="text-sm font-semibold" style={{ color:
statusConfig[[Link]].color }}>{statusConfig[[Link]].label}</span>
</div>
</div>
</button>
</div>
)
})}
</div>
) : (
<p className="text-center text-[var(--text-secondary)] py-8">No lessons
have been categorized yet.</p>
)}
</div>
</div>
);
};
interface EgyptianExamHistoryModalProps {
questions: PracticeQuestion[];
onClose: () => void;
}
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)]">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Review
Egyptian University Exam Questions</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto">
{[Link] === 0 ? (
<p className="text-center text-[var(--text-secondary)]">No Egyptian
exam questions have been solved for this page yet.</p>
) : (
<div className="space-y-6">
{[Link]((q, qIndex) => (
<div key={qIndex} className="bg-[var(--bg-main)] p-4 rounded-lg">
<p className="text-lg text-[var(--text-primary)] mb-2">{qIndex +
1}. {[Link]}</p>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-
2">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-
[var(--accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, oIndex) => {
const isCorrect = option === [Link];
return (
<div
key={oIndex}
className={`w-full p-3 text-left rounded-lg border $
{isCorrect ? 'bg-green-500/30 border-green-500' : 'bg-[var(--bg-alt)] border-
[var(--border-color)] opacity-70'}`}
>
{option}
</div>
);
})}
</div>
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4
border-green-500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose
prose-invert max-w-none" dangerouslySetInnerHTML={{ __html:
renderExplanation([Link]) }}/>
</div>
</div>
))}
</div>
)}
</main>
<footer className="p-4 border-t border-[var(--border-color)]">
<button
onClick={onClose}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white font-
bold rounded-lg hover:opacity-90 transition-opacity"
>
Close
</button>
</footer>
</div>
</div>
);
};
interface EgyptianExamModalProps {
fileName: string;
lessonId: string;
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
}
useEffect(() => {
const loadedSession = getQuizSession(lessonId, 'egyptianExams');
if (loadedSession) {
setSession(loadedSession);
}
}, [lessonId, fileName, getQuizSession]);
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Egyptian
University Exams</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{renderContent()}
</main>
<footer className="p-4 border-t border-[var(--border-color)] flex-shrink-
0">
{isAnswered && session && (
<button
onClick={handleNextQuestion}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white
font-bold rounded-lg hover:opacity-90 transition-opacity"
>
{[Link] < [Link] - 1 ? 'Next
Question' : 'Finish'}
</button>
)}
</footer>
</div>
</div>
);
};
interface ExamUploaderProps {
onExamsProcessed: (exams: UserExam[]) => void;
}
[Link] = '[Link]
dist@4.5.136/build/[Link]';
const fileToBase64 = (file: File): Promise<{ base64: string; mimeType: string }> =>
{
return new Promise((resolve, reject) => {
const reader = new FileReader();
[Link] = () => {
const result = [Link] as string;
resolve({ base64: [Link](',')[1], mimeType: [Link] });
};
[Link] = error => reject(error);
[Link](file);
});
};
setError(null);
setIsProcessing(true);
setStatusMessage('Starting exam processing...');
if ([Link]('.pdf')) {
fileText = await processPdf(file);
} else if ([Link]('.docx')) {
fileText = await processDocx(file);
} else if ([Link]('image/')) {
setStatusMessage(`Extracting text from image ${[Link]}... (this may
take a moment)`);
const { base64, mimeType } = await fileToBase64(file);
fileText = await extractTextFromImage(base64, mimeType);
} else {
[Link](`Unsupported file type: ${[Link]}. Skipping.`);
continue;
}
if (![Link]()) {
[Link](`No text extracted from ${[Link]}. Skipping.`);
continue;
}
if ([Link] > 0) {
[Link]({
id: `exam-${[Link]()}-${i}`,
name: [Link],
questions: questions,
});
}
} catch (err) {
const errorMessage = err instanceof Error ? [Link] : String(err);
[Link](`Failed to process ${[Link]}:`, err);
setError(prev => `${prev ? prev + '\n' : ''}Error on ${[Link]}: $
{errorMessage}`);
}
}
if ([Link] > 0) {
onExamsProcessed(processedExams);
}
setIsProcessing(false);
setStatusMessage('');
if ([Link]) {
[Link] = '';
}
};
return (
<div className="bg-[var(--bg-main)] p-4 rounded-lg border border-[var(--border-
color)] text-center">
{isProcessing ? (
<>
<Loader />
<p className="mt-2 text-sm text-[var(--text-
secondary)]">{statusMessage}</p>
</>
) : (
<>
<div className="mx-auto h-12 w-12 text-[var(--accent-primary)]">
<UploadCloudIcon />
</div>
<h4 className="mt-2 text-md font-semibold text-[var(--text-
primary)]">Upload Exam Materials</h4>
<p className="mt-1 text-xs text-[var(--text-secondary)]">Select
individual files or a whole folder.</p>
<div className="mt-4 flex flex-col sm:flex-row gap-2 justify-center">
<label htmlFor="exam-file-upload" className="cursor-pointer w-full
flex-1 text-center px-4 py-2 text-sm bg-[var(--accent-primary)] text-white font-
bold rounded-lg hover:opacity-90 transition-opacity flex items-center justify-
center">
Select Files
<input
id="exam-file-upload"
name="exam-file-upload"
type="file"
className="sr-only"
multiple
accept=".pdf,.docx,application/pdf,application/[Link]-
[Link],image/png,image/jpeg"
onChange={handleFileChange}
/>
</label>
<label htmlFor="exam-folder-upload" className="cursor-pointer w-full
flex-1 text-center px-4 py-2 text-sm bg-[var(--bg-main)] border border-[var(--
border-color)] text-[var(--text-secondary)] font-bold rounded-lg hover:text-[var(--
text-primary)] hover:border-[var(--text-accent)] transition-colors flex items-
center justify-center">
Select Folder
<input
id="exam-folder-upload"
name="exam-folder-upload"
type="file"
className="sr-only"
// FIX: The `webkitdirectory` attribute is non-standard and not in
React's TypeScript types.
// Using an object spread is a workaround to bypass the type check.
{...{ webkitdirectory: "" }}
onChange={handleFileChange}
/>
</label>
</div>
</>
)}
{error && <p className="mt-2 text-xs text-left text-[var(--accent-danger)]
whitespace-pre-wrap">{error}</p>}
</div>
);
};
interface ImpossibleHistoryModalProps {
questions: PracticeQuestion[];
onClose: () => void;
}
const ImpossibleHistoryModal: [Link]<ImpossibleHistoryModalProps> = ({ questions,
onClose }) => {
const renderExplanation = (explanation: string) => {
try {
return [Link](explanation);
} catch (e) {
return explanation;
}
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)]">
<div className="flex items-center gap-2">
<span className="text-red-500"><SkullIcon /></span>
<h2 className="text-xl font-bold text-[var(--text-primary)]">Review
Impossible Questions</h2>
</div>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto">
{[Link] === 0 ? (
<p className="text-center text-[var(--text-secondary)]">No impossible
questions have been solved for this page yet.</p>
) : (
<div className="space-y-6">
{[Link]((q, qIndex) => (
<div key={qIndex} className="bg-[var(--bg-main)] p-4 rounded-lg">
<p className="text-lg text-[var(--text-primary)] mb-2">{qIndex +
1}. {[Link]}</p>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-
2">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-
[var(--accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, oIndex) => {
const isCorrect = option === [Link];
return (
<div
key={oIndex}
className={`w-full p-3 text-left rounded-lg border $
{isCorrect ? 'bg-green-500/30 border-green-500' : 'bg-[var(--bg-alt)] border-
[var(--border-color)] opacity-70'}`}
>
{option}
</div>
);
})}
</div>
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4
border-green-500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose
prose-invert max-w-none" dangerouslySetInnerHTML={{ __html:
renderExplanation([Link]) }}/>
</div>
</div>
))}
</div>
)}
</main>
<footer className="p-4 border-t border-[var(--border-color)]">
<button
onClick={onClose}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white font-
bold rounded-lg hover:opacity-90 transition-opacity"
>
Close
</button>
</footer>
</div>
</div>
);
};
interface ImpossibleModalProps {
fileName: string;
lessonId: string;
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
}
useEffect(() => {
const loadedSession = getQuizSession(lessonId, 'impossible');
if (loadedSession) {
setSession(loadedSession);
}
}, [lessonId, fileName, getQuizSession]);
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
return (
<div>
<div className="flex items-start gap-3 mb-2">
<p className="text-lg text-[var(--text-primary)] font-bold pt-
1.5">{[Link] + 1}.</p>
<p className="text-lg text-[var(--text-
primary)]">{[Link]}</p>
</div>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-2 ml-8">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-4 ml-8">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-[var(--
accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswered}
className={`w-full p-3 text-left rounded-lg border border-[var(--
border-color)] transition-all ${getButtonClass(option)}`}
>
{option}
</button>
))}
</div>
{isAnswered && (
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4 border-green-
500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose prose-invert
max-w-none" dangerouslySetInnerHTML={{ __html: explanationHtml }}/>
{!([Link] ||
[]).includes([Link]) && (
<div className="mt-2 pt-2 border-t border-[var(--border-color)]">
<h5 className="font-bold text-yellow-400">Correct Answer</h5>
<p className="text-sm text-[var(--text-
secondary)]">{[Link]}</p>
</div>
)}
</div>
)}
</div>
);
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<div className="flex items-center gap-2">
<span className="text-red-500"><SkullIcon /></span>
<h2 className="text-xl font-bold text-[var(--text-primary)]">Impossible
Questions</h2>
</div>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{renderContent()}
</main>
<footer className="p-4 border-t border-[var(--border-color)] flex-shrink-
0">
{isAnswered && session && (
<button
onClick={handleNextQuestion}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white
font-bold rounded-lg hover:opacity-90 transition-opacity"
>
{[Link] < [Link] - 1 ? 'Next
Question' : 'Finish'}
</button>
)}
</footer>
</div>
</div>
);
};
export default ImpossibleModal;
--- END OF FILE components/[Link] ---
interface McqHistoryModalProps {
questions: PracticeQuestion[];
onClose: () => void;
}
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)]">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Review Study
Questions</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto">
{[Link] === 0 ? (
<p className="text-center text-[var(--text-secondary)]">No study
questions have been saved for this page yet.</p>
) : (
<div className="space-y-6">
{[Link]((q, qIndex) => (
<div key={qIndex} className="bg-[var(--bg-main)] p-4 rounded-lg">
<p className="text-lg text-[var(--text-primary)] mb-2">{qIndex +
1}. {[Link]}</p>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-
2">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-
[var(--accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, oIndex) => {
const isCorrect = option === [Link];
return (
<div
key={oIndex}
className={`w-full p-3 text-left rounded-lg border $
{isCorrect ? 'bg-green-500/30 border-green-500' : 'bg-[var(--bg-alt)] border-
[var(--border-color)] opacity-70'}`}
>
{option}
</div>
);
})}
</div>
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4
border-green-500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose
prose-invert max-w-none" dangerouslySetInnerHTML={{ __html:
renderExplanation([Link]) }}/>
</div>
</div>
))}
</div>
)}
</main>
<footer className="p-4 border-t border-[var(--border-color)]">
<button
onClick={onClose}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white font-
bold rounded-lg hover:opacity-90 transition-opacity"
>
Close
</button>
</footer>
</div>
</div>
);
};
interface MockExamModalProps {
questions: PracticeQuestion[];
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
title?: string;
}
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
if ([Link] === 0) {
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center
p-4 z-50">
<div className="w-full max-w-lg bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)]">
<header className="p-4 flex justify-between items-center border-b
border-[var(--border-color)]">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Exam
Notice</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]"><XIcon /></button>
</header>
<main className="p-6 text-center">
<p className="text-[var(--text-secondary)]">No questions were
generated. Please check your selections and try again.</p>
</main>
<footer className="p-4 border-t border-[var(--border-color)]">
<button onClick={onClose} className="w-full py-2 px-4 bg-[var(--
accent-primary)] text-white font-bold rounded-lg hover:opacity-90 transition-
opacity">Close</button>
</footer>
</div>
</div>
);
}
return (
<>
<div>
<div className="flex items-start gap-3 mb-2">
<p className="text-lg text-[var(--text-primary)] font-bold pt-
1.5">{currentIndex + 1}.</p>
<p className="text-lg text-[var(--text-
primary)]">{[Link]}</p>
</div>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-2
ml-8">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-4 ml-8">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-
[var(--accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswered}
className={`w-full p-3 text-left rounded-lg border border-
[var(--border-color)] transition-all ${getButtonClass(option, currentQuestion,
answers[currentIndex])}`}
>
{option}
</button>
))}
</div>
{isAnswered && (
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4 border-
green-500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose
prose-invert max-w-none" dangerouslySetInnerHTML={{ __html: explanationHtml }}/>
</div>
)}
</div>
<footer className="p-4 border-t border-[var(--border-color)] flex-
shrink-0 mt-6 -mx-6 -mb-6">
{isAnswered && (
<button onClick={handleNextQuestion} className="w-full py-2 px-4
bg-[var(--accent-primary)] text-white font-bold rounded-lg hover:opacity-90
transition-opacity">
{currentIndex < [Link] - 1 ? 'Next Question' : 'Finish
Exam'}
</button>
)}
</footer>
</>
);
};
return (
<>
<div className="text-center">
<h3 className="text-2xl font-bold text-[var(--text-primary)]">Exam
Complete!</h3>
<p className="text-lg text-[var(--text-accent)] mt-2">Your Score:
{correctAnswersCount} / {[Link]}</p>
</div>
<div className="mt-6 space-y-6">
<h4 className="text-xl font-bold text-[var(--text-primary)]">Review
Your Answers</h4>
{[Link]((q, qIndex) => (
<div key={qIndex} className="bg-[var(--bg-main)] p-4 rounded-
lg">
<p className="text-lg text-[var(--text-primary)] mb-
2">{qIndex + 1}. {[Link]}</p>
{[Link] && <p className="text-xs text-[var(--text-
secondary)] italic mb-2">({[Link]})</p>}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs
bg-[var(--accent-primary)]/20 text-[var(--accent-primary)]
rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, oIndex) => {
const isCorrect = option === [Link];
const isUserChoice = option === answers[qIndex];
let borderClass = 'border-[var(--border-color)]';
if (isCorrect) borderClass = 'border-green-500';
else if (isUserChoice) borderClass = 'border-red-
500';
return (
<div key={oIndex} className={`w-full p-3 text-
left rounded-lg border-2 ${borderClass} ${isCorrect ? 'bg-green-500/20' :
isUserChoice ? 'bg-red-500/20' : 'bg-[var(--bg-alt)] opacity-80'}`}>
{option}
{isCorrect && <span className="text-xs
font-bold ml-2 text-green-400">(Correct Answer)</span>}
{!isCorrect && isUserChoice && <span
className="text-xs font-bold ml-2 text-red-400">(Your Answer)</span>}
</div>
);
})}
</div>
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4
border-green-500">
<h4 className="font-bold
text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)]
prose prose-invert max-w-none" dangerouslySetInnerHTML={{ __html:
renderExplanation([Link]) }}/>
</div>
</div>
))}
</div>
<footer className="p-4 border-t border-[var(--border-color)] flex-
shrink-0 mt-6 -mx-6 -mb-6">
<button onClick={handleFinishAndClose} className="w-full py-2 px-4
bg-[var(--accent-primary)] text-white font-bold rounded-lg hover:opacity-90
transition-opacity">
Close
</button>
</footer>
</>
);
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-3xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<h2 className="text-xl font-bold text-[var(--text-primary)]">{title ||
'Mock Exam'}</h2>
{!isFinished && <div className="text-sm text-[var(--text-
secondary)]">Question {currentIndex + 1} of {[Link]}</div>}
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]"><XIcon /></button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{isFinished ? renderResultsView() : renderQuestionView()}
</main>
</div>
</div>
);
};
interface PdfProcessorProps {
onProcessingStart: () => void;
onFilesProcessed: (documents: Document[]) => void;
}
// Set the worker source for the ES module version of [Link]
[Link] = '[Link]
dist@4.5.136/build/[Link]';
const fileToBase64 = (file: File): Promise<{ base64: string; mimeType: string }> =>
{
return new Promise((resolve, reject) => {
const reader = new FileReader();
[Link] = () => {
const result = [Link] as string;
resolve({ base64: [Link](',')[1], mimeType: [Link] });
};
[Link] = error => reject(error);
[Link](file);
});
};
let textCursor = 0;
let pageBreakIndex;
if (splitIndex > textCursor) {
pageBreakIndex = splitIndex + 1;
} else {
pageBreakIndex = textCursor + TARGET_PAGE_SIZE;
}
setError(null);
setIsProcessing(true);
onProcessingStart();
try {
const fileType = [Link];
const fileName = [Link]();
let pages: string[] = [];
} catch (err) {
[Link](`Error processing ${[Link]}:`, err);
let errorMessage = err instanceof Error ? [Link] : String(err);
setError(prev => `${prev ? prev + '\n' : ''}Failed to process $
{[Link]}. ${errorMessage}`);
}
}
if ([Link] > 0) {
onFilesProcessed(processedDocuments);
} else if (!error) {
setError("No content could be extracted from the selected files. Please
try different files.");
}
setIsProcessing(false);
setStatusMessage('');
};
[Link]();
return (
<div className="flex items-center justify-center h-full w-full p-4">
<div className="w-full max-w-lg text-center p-8 rounded-2xl border border-
[var(--border-color)] bg-[var(--bg-alt)] shadow-2xl shadow-[var(--shadow-color)]">
{isProcessing ? (
<>
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-
4">Processing Your Documents...</h2>
<p className="text-[var(--text-secondary)] mb-6">{statusMessage ||
"Please wait."}</p>
<Loader />
</>
) : (
<>
<div className="mx-auto h-20 w-20 text-[var(--accent-primary)]">
<UploadCloudIcon />
</div>
<h2 className="mt-6 text-2xl font-bold tracking-tight text-[var(--text-
primary)]">Upload your study materials</h2>
<p className="mt-2 text-base text-[var(--text-secondary)]">Drag and
drop your PDF, .docx, or image files here, or click to browse.</p>
{error && <p className="mt-4 text-sm text-[var(--accent-danger)]
whitespace-pre-wrap">{error}</p>}
<div className="mt-6 flex flex-col items-center gap-4">
<label htmlFor="file-upload" className="cursor-pointer relative
inline-flex items-center justify-center px-8 py-3 text-lg font-bold text-white bg-
[var(--accent-primary)] rounded-full transition-transform duration-300 hover:scale-
105 shadow-lg w-full max-w-xs">
<span>Select Documents</span>
<input id="file-upload" name="file-upload" type="file"
className="sr-only"
accept=".pdf,.docx,application/pdf,application/[Link]-
[Link],image/png,image/jpeg"
onChange={handleFileChange} multiple />
</label>
<div className="text-sm text-[var(--text-secondary)]">or</div>
<label htmlFor="import-data" className="cursor-pointer inline-flex
items-center justify-center px-6 py-2 text-sm font-bold text-[var(--text-
secondary)] bg-[var(--bg-main)] border border-[var(--border-color)] rounded-full
transition-colors hover:text-[var(--text-primary)] hover:border-[var(--text-
accent)] w-full max-w-xs">
<UploadIcon />
<span className="ml-2">Import Progress</span>
<input id="import-data" name="import-data" type="file"
className="sr-only" accept=".json" onChange={handleImportData} />
</label>
</div>
</>
)}
</div>
</div>
);
};
interface PracticeModalProps {
fileName: string;
lessonId: string;
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
}
useEffect(() => {
const loadedSession = getQuizSession(lessonId, 'october6Exam');
if (loadedSession) {
setSession(loadedSession);
}
}, [lessonId, fileName, getQuizSession]);
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
<div className="space-y-3">
{([Link] || []).map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswered}
className={`w-full p-3 text-left rounded-lg border border-[var(--
border-color)] transition-all ${getButtonClass(option)}`}
>
{option}
</button>
))}
</div>
{isAnswered && (
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4 border-green-
500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose prose-invert
max-w-none" dangerouslySetInnerHTML={{ __html: explanationHtml }}/>
{!([Link] ||
[]).includes([Link]) && (
<div className="mt-2 pt-2 border-t border-[var(--border-color)]">
<h5 className="font-bold text-yellow-400">Correct Answer</h5>
<p className="text-sm text-[var(--text-
secondary)]">{[Link]}</p>
</div>
)}
</div>
)}
</div>
);
};
interface RealExamHistoryModalProps {
questions: PracticeQuestion[];
onClose: () => void;
}
interface RealExamModalProps {
fileName: string;
lessonId: string;
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
}
useEffect(() => {
const loadedSession = getQuizSession(lessonId, 'realExam');
if (loadedSession) {
setSession(loadedSession);
}
}, [lessonId, fileName, getQuizSession]);
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
return (
<div>
<div className="flex items-start gap-3 mb-2">
<p className="text-lg text-[var(--text-primary)] font-bold pt-
1.5">{[Link] + 1}.</p>
<p className="text-lg text-[var(--text-
primary)]">{[Link]}</p>
</div>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-2 ml-8">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-4 ml-8">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-[var(--
accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswered}
className={`w-full p-3 text-left rounded-lg border border-[var(--
border-color)] transition-all ${getButtonClass(option)}`}
>
{option}
</button>
))}
</div>
{isAnswered && (
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4 border-green-
500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose prose-invert
max-w-none" dangerouslySetInnerHTML={{ __html: explanationHtml }}/>
{!([Link] ||
[]).includes([Link]) && (
<div className="mt-2 pt-2 border-t border-[var(--border-color)]">
<h5 className="font-bold text-yellow-400">Correct Answer</h5>
<p className="text-sm text-[var(--text-
secondary)]">{[Link]}</p>
</div>
)}
</div>
)}
</div>
);
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Real Exam
Questions</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{renderContent()}
</main>
<footer className="p-4 border-t border-[var(--border-color)] flex-shrink-
0">
{isAnswered && session && (
<button
onClick={handleNextQuestion}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white
font-bold rounded-lg hover:opacity-90 transition-opacity"
>
{[Link] < [Link] - 1 ? 'Next
Question' : 'Finish'}
</button>
)}
</footer>
</div>
</div>
);
};
interface RecallModalProps {
originalText: string;
onClose: () => void;
}
const RecallModal: [Link]<RecallModalProps> = ({ originalText, onClose }) => {
const [recallText, setRecallText] = useState('');
const [analysis, setAnalysis] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-3xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Test Your
Recall</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{analysis ? (
<div>
<h3 className="text-lg font-bold mb-2 text-[var(--text-accent)]">AI
Feedback</h3>
<div className="prose prose-invert max-w-none p-4 bg-[var(--bg-main)]
rounded-lg" dangerouslySetInnerHTML={{ __html: analysisHtml }} />
</div>
) : (
<div>
<label htmlFor="recall-input" className="block text-sm font-medium
text-[var(--text-secondary)] mb-2">
Write down everything you remember from this page. Don't worry
about getting it perfect.
</label>
<textarea
id="recall-input"
rows={10}
value={recallText}
onChange={(e) => setRecallText([Link])}
className="w-full p-3 bg-[var(--bg-main)] border border-[var(--
border-color)] rounded-lg focus:ring-2 focus:ring-[var(--accent-primary)]
focus:outline-none"
placeholder="Start writing here..."
disabled={isLoading}
/>
{error && <p className="mt-2 text-sm text-[var(--accent-
danger)]">{error}</p>}
</div>
)}
</main>
<footer className="p-4 border-t border-[var(--border-color)] flex-shrink-
0">
{analysis ? (
<button
onClick={onClose}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white
font-bold rounded-lg hover:opacity-90 transition-opacity"
>
Close
</button>
) : (
<button
onClick={handleSubmit}
disabled={isLoading || [Link]().length === 0}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white
font-bold rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50
disabled:cursor-not-allowed flex justify-center items-center"
>
{isLoading ? <Loader /> : 'Analyze My Recall'}
</button>
)}
</footer>
</div>
</div>
);
};
interface ReviewMistakesModalProps {
title: string;
questions: PracticeQuestion[];
onClose: () => void;
}
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-3xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)]">
<div className="flex items-center gap-2">
<HistoryIcon />
<h2 className="text-xl font-bold
text-[var(--text-primary)]">{title}</h2>
</div>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto">
{[Link] === 0 ? (
<p className="text-center text-[var(--text-secondary)] py-8">No
questions to review in this category.</p>
) : (
<div className="space-y-6">
{[Link]((q, qIndex) => (
<div key={qIndex} className="bg-[var(--bg-main)] p-4 rounded-lg">
<p className="text-lg text-[var(--text-primary)] mb-2">{qIndex +
1}. {[Link]}</p>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-
2">
(From: {[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-
[var(--accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{[Link]((option, oIndex) => {
const isCorrect = option === [Link];
const isUserAnswer = option === [Link];
interface StudyByQuestionsModalProps {
fileName: string;
lessonId: string;
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
}
useEffect(() => {
const loadedSession = getQuizSession(lessonId, 'studyMcq');
if (loadedSession) {
setSession(loadedSession);
}
}, [lessonId, fileName, getQuizSession]);
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
return (
<div>
<div className="flex items-start gap-3 mb-2">
<p className="text-lg text-[var(--text-primary)] font-bold pt-
1.5">{[Link] + 1}.</p>
<p className="text-lg text-[var(--text-
primary)]">{[Link]}</p>
</div>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-2 ml-8">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-4 ml-8">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-[var(--
accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswered}
className={`w-full p-3 text-left rounded-lg border border-[var(--
border-color)] transition-all ${getButtonClass(option)}`}
>
{option}
</button>
))}
</div>
{isAnswered && (
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4 border-green-
500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose prose-invert
max-w-none" dangerouslySetInnerHTML={{ __html: explanationHtml }}/>
{!([Link] ||
[]).includes([Link]) && (
<div className="mt-2 pt-2 border-t border-[var(--border-color)]">
<h5 className="font-bold text-yellow-400">Correct Answer</h5>
<p className="text-sm text-[var(--text-
secondary)]">{[Link]}</p>
</div>
)}
</div>
)}
</div>
);
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<h2 className="text-xl font-bold text-[var(--text-primary)]">Study by
Questions</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{renderContent()}
</main>
<footer className="p-4 border-t border-[var(--border-color)] flex-shrink-
0">
{isAnswered && session && (
<button
onClick={handleNextQuestion}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white
font-bold rounded-lg hover:opacity-90 transition-opacity"
>
{[Link] < [Link] - 1 ? 'Next
Question' : 'Finish'}
</button>
)}
</footer>
</div>
</div>
);
};
interface StudyHighlightsHistoryModalProps {
questions: PracticeQuestion[];
onClose: () => void;
title?: string;
}
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)]">
<h2 className="text-xl font-bold text-[var(--text-primary)]">{title}</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto">
{[Link] === 0 ? (
<p className="text-center text-[var(--text-secondary)]">No questions
have been saved for this page yet.</p>
) : (
<div className="space-y-6">
{[Link]((q, qIndex) => (
<div key={qIndex} className="bg-[var(--bg-main)] p-4 rounded-lg">
<p className="text-lg text-[var(--text-primary)] mb-2">{qIndex +
1}. {[Link]}</p>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-
2">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-
[var(--accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, oIndex) => {
const isCorrect = option === [Link];
return (
<div
key={oIndex}
className={`w-full p-3 text-left rounded-lg border $
{isCorrect ? 'bg-green-500/30 border-green-500' : 'bg-[var(--bg-alt)] border-
[var(--border-color)] opacity-70'}`}
>
{option}
</div>
);
})}
</div>
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4
border-green-500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose
prose-invert max-w-none" dangerouslySetInnerHTML={{ __html:
renderExplanation([Link]) }}/>
</div>
</div>
))}
</div>
)}
</main>
<footer className="p-4 border-t border-[var(--border-color)]">
<button
onClick={onClose}
className="w-full py-2 px-4 bg-[var(--accent-primary)] text-white font-
bold rounded-lg hover:opacity-90 transition-opacity"
>
Close
</button>
</footer>
</div>
</div>
);
};
interface StudyHighlightsModalProps {
fileName: string;
lessonId: string;
onClose: () => void;
onComplete: (answeredQuestions: PracticeQuestion[]) => void;
quizType?: QuizType;
title?: string;
}
useEffect(() => {
const loadedSession = getQuizSession(lessonId, quizType);
if (loadedSession) {
setSession(loadedSession);
}
}, [lessonId, fileName, getQuizSession, quizType]);
if (!isAnswered) {
return 'bg-[var(--bg-alt)] hover:bg-[var(--border-color)]';
}
if (option === [Link]) {
return 'bg-green-500/50 border-green-500';
}
if (option === selectedAnswer) {
return 'bg-red-500/50 border-red-500';
}
return 'bg-[var(--bg-alt)] opacity-60';
};
return (
<div>
<div className="flex items-start gap-3 mb-2">
<p className="text-lg text-[var(--text-primary)] font-bold pt-
1.5">{[Link] + 1}.</p>
<p className="text-lg text-[var(--text-
primary)]">{[Link]}</p>
</div>
{[Link] && (
<p className="text-xs text-[var(--text-secondary)] italic mb-2 ml-8">
({[Link]})
</p>
)}
{[Link] && [Link] > 0 && (
<div className="flex flex-wrap gap-2 mb-4 ml-8">
{[Link](tag => (
<span key={tag} className="px-2 py-0.5 text-xs bg-[var(--
accent-primary)]/20 text-[var(--accent-primary)] rounded-full">{tag}</span>
))}
</div>
)}
<div className="space-y-3">
{([Link] || []).map((option, index) => (
<button
key={index}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswered}
className={`w-full p-3 text-left rounded-lg border border-[var(--
border-color)] transition-all ${getButtonClass(option)}`}
>
{option}
</button>
))}
</div>
{isAnswered && (
<div className="mt-4 p-3 bg-black/20 rounded-lg border-l-4 border-green-
500">
<h4 className="font-bold text-green-400">Explanation</h4>
<div className="text-sm text-[var(--text-secondary)] prose prose-invert
max-w-none" dangerouslySetInnerHTML={{ __html: explanationHtml }}/>
{!([Link] ||
[]).includes([Link]) && (
<div className="mt-2 pt-2 border-t border-[var(--border-color)]">
<h5 className="font-bold text-yellow-400">Correct Answer</h5>
<p className="text-sm text-[var(--text-
secondary)]">{[Link]}</p>
</div>
)}
</div>
)}
</div>
);
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4
z-50">
<div className="w-full max-w-2xl bg-[var(--bg-alt)] rounded-2xl shadow-2xl
border border-[var(--border-color)] flex flex-col max-h-[90vh]">
<header className="p-4 flex justify-between items-center border-b border-
[var(--border-color)] flex-shrink-0">
<h2 className="text-xl font-bold text-[var(--text-primary)]">{title}</h2>
<button onClick={onClose} className="text-[var(--text-secondary)]
hover:text-[var(--text-primary)]">
<XIcon />
</button>
</header>
<main className="p-6 overflow-y-auto flex-grow">
{renderContent()}