import { useState, useRef, useEffect, useCallback } from "react";
// ── Icons ──────────────────────────────────────────────────────────────────
const Icon = ({ d, size = 20, className = "" }) => (
);
const Icons = {
math: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5",
science: "M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 0 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 0-2-2V9m0 0h18",
english: "M4 6h16M4 12h16M4 18h7",
social: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm14 10v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75",
send: "M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z",
image: "M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3 3.5-4.5 4.5 6H5l3.5-4.5z",
camera: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2zM12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z",
copy: "M20 9H11a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2zM5 15H4a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h3a2 2 0 0 0 2-2v-1",
download: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3",
share: "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13",
history: "M3 3h6M3 8h9M3 13h5M12 17l1-5 4 2-5 3zM22 17l-1-5-4 2 5 3z",
settings: "M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z",
home: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2zM9 22V12h6v10",
back: "M19 12H5M12 19l-7-7 7-7",
moon: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z",
sun: "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z",
trash: "M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6",
star: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
check: "M20 6L9 17l-5-5",
x: "M18 6L6 18M6 6l12 12",
user: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z",
logout: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9",
google: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z",
book: "M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 4.5A2.5 2.5 0 0 0 6.5 7H20v13H6.5A2.5 2.5 0 0 1 4 17.5v-13z",
sparkle: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",
attach: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48",
};
// ── Storage ────────────────────────────────────────────────────────────────
const STORAGE_KEY = "ai_study_helper_v1";
const loadData = () => {
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}"); } catch { return {}; }
};
const saveData = (data) => {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch {}
};
// ── Subjects Config ────────────────────────────────────────────────────────
const SUBJECTS = [
{ id: "math", name: "Mathematics", nameHi: "เคเคฃिเคค", icon: "math", color: "#6366f1", bg: "#eef2ff", darkBg: "#1e1b4b", emoji: "๐", desc: "Algebra, Geometry, Calculus & more", descHi: "เคฌीเคเคเคฃिเคค, เค्เคฏाเคฎिเคคि, เคเคฒเคจ เคเคฐ เค
เคงिเค" },
{ id: "science", name: "Science", nameHi: "เคตिเค्เคाเคจ", icon: "science", color: "#10b981", bg: "#ecfdf5", darkBg: "#064e3b", emoji: "๐ฌ", desc: "Physics, Chemistry, Biology", descHi: "เคญौเคคिเคी, เคฐเคธाเคฏเคจ, เคीเคต เคตिเค्เคाเคจ" },
{ id: "english", name: "English", nameHi: "เค
ंเค्เคฐेเค़ी", icon: "english", color: "#f59e0b", bg: "#fffbeb", darkBg: "#451a03", emoji: "๐", desc: "Grammar, Literature, Writing", descHi: "เคต्เคฏाเคเคฐเคฃ, เคธाเคนिเคค्เคฏ, เคฒेเคเคจ" },
{ id: "social", name: "Social Studies", nameHi: "เคธाเคฎाเคिเค เค
เคง्เคฏเคฏเคจ", icon: "social", color: "#ec4899", bg: "#fdf2f8", darkBg: "#500724", emoji: "๐", desc: "History, Geography, Civics", descHi: "เคเคคिเคนाเคธ, เคญूเคोเคฒ, เคจाเคเคฐिเคเคถाเคธ्เคค्เคฐ" },
];
// ── API Call ───────────────────────────────────────────────────────────────
async function askAI(subject, question, imageBase64, lang) {
const subj = SUBJECTS.find(s => s.id === subject);
const langNote = lang === "hi" ? "Reply in Hindi (Devanagari script) with English technical terms where needed." : "Reply in English. You may include Hindi translations for key terms in brackets.";
const systemPrompt = `You are an expert ${subj.name} teacher for school and college students.
${langNote}
Format your answer with:
1. **Brief Problem Understanding** (1-2 lines)
2. **Step-by-Step Solution** (numbered steps, each explained simply)
3. **Key Concepts Used** (bullet points)
4. **Final Answer** (highlighted clearly)
5. **Pro Tip** (one helpful tip related to this topic)
Use simple language. If math, show all calculations. Be encouraging and student-friendly.`;
const content = [];
if (imageBase64) {
content.push({ type: "image", source: { type: "base64", media_type: "image/jpeg", data: imageBase64 } });
content.push({ type: "text", text: question ? `Question from image: ${question}` : "Please read this image, identify the question/problem shown, and solve it step by step." });
} else {
content.push({ type: "text", text: question });
}
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 1000,
system: systemPrompt,
messages: [{ role: "user", content }],
}),
});
const data = await res.json();
return data.content?.[0]?.text || "Sorry, could not generate an answer. Please try again.";
}
// ── Step Colors (cycling) ──────────────────────────────────────────────────
const STEP_COLORS = ["#c084fc", "#60a5fa", "#34d399", "#fb923c", "#f472b6", "#a78bfa"];
// ── Inline Bold helper ─────────────────────────────────────────────────────
function InlineBold({ text, color }) {
const parts = text.split(/\*\*(.*?)\*\*/g);
return (
<>
{parts.map((p, j) =>
j % 2 === 1
? {p}
: p
)}
</>
);
}
// ── Answer Card Renderer — image-style dark card ───────────────────────────
function renderAnswer(text, dark, subjectColor) {
const lines = text.split("\n").filter((l, i, arr) => !(l.trim() === "" && arr[i - 1]?.trim() === ""));
const accent = subjectColor || "#a855f7";
let stepCount = 0;
return (
{lines.map((line, i) => {
const trimmed = line.trim();
if (!trimmed) return
;
// ── Section header (## or # or **Header**)
if (trimmed.startsWith("## ") || trimmed.startsWith("# ")) {
const txt = trimmed.replace(/^#+\s*/, "").replace(/\*\*/g, "");
return (
);
}
// ── Main App ───────────────────────────────────────────────────────────────
export default function App() {
const stored = loadData();
const [page, setPage] = useState("home");
const [subject, setSubject] = useState(null);
const [dark, setDark] = useState(stored.dark ?? false);
const [lang, setLang] = useState(stored.lang ?? "en");
const [chats, setChats] = useState(stored.chats ?? {});
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [image, setImage] = useState(null);
const [imagePreview, setImagePreview] = useState(null);
const [user, setUser] = useState(stored.user ?? null);
const [copied, setCopied] = useState(null);
const [historyFilter, setHistoryFilter] = useState("all");
const [cameraOpen, setCameraOpen] = useState(false);
const chatEndRef = useRef(null);
const fileRef = useRef(null);
const videoRef = useRef(null);
const canvasRef = useRef(null);
const streamRef = useRef(null);
// persist
useEffect(() => { saveData({ dark, lang, chats, user }); }, [dark, lang, chats, user]);
useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [chats, subject]);
const subj = SUBJECTS.find(s => s.id === subject);
const subjectChats = subject ? (chats[subject] ?? []) : [];
// ── Camera ──────────────────────────────────────────────────────────────
const openCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
streamRef.current = stream;
setCameraOpen(true);
setTimeout(() => { if (videoRef.current) videoRef.current.srcObject = stream; }, 100);
} catch { alert("Camera access denied or not available."); }
};
const capturePhoto = () => {
const v = videoRef.current, c = canvasRef.current;
if (!v || !c) return;
c.width = v.videoWidth; c.height = v.videoHeight;
c.getContext("2d").drawImage(v, 0, 0);
const dataUrl = c.toDataURL("image/jpeg", 0.85);
setImagePreview(dataUrl);
setImage(dataUrl.split(",")[1]);
closeCamera();
};
const closeCamera = () => {
streamRef.current?.getTracks().forEach(t => t.stop());
setCameraOpen(false);
};
// ── File Upload ──────────────────────────────────────────────────────────
const handleFile = (file) => {
if (!file) return;
const r = new FileReader();
r.onload = (e) => {
const base64 = e.target.result.split(",")[1];
setImage(base64);
setImagePreview(e.target.result);
};
r.readAsDataURL(file);
};
// ── Send ─────────────────────────────────────────────────────────────────
const send = async () => {
if (!input.trim() && !image) return;
const q = input.trim();
const imgB64 = image;
const imgPrev = imagePreview;
setInput(""); setImage(null); setImagePreview(null);
setLoading(true);
const userMsg = { role: "user", text: q, image: imgPrev, ts: Date.now() };
setChats(prev => ({ ...prev, [subject]: [...(prev[subject] ?? []), userMsg] }));
try {
const answer = await askAI(subject, q || "Solve this problem from the image", imgB64, lang);
const aiMsg = { role: "ai", text: answer, ts: Date.now(), subject };
setChats(prev => ({ ...prev, [subject]: [...(prev[subject] ?? []), aiMsg] }));
} catch {
const errMsg = { role: "ai", text: "❌ Network error. Please check your connection and try again.", ts: Date.now(), subject };
setChats(prev => ({ ...prev, [subject]: [...(prev[subject] ?? []), errMsg] }));
}
setLoading(false);
};
// ── Copy ─────────────────────────────────────────────────────────────────
const copyText = (text, id) => {
navigator.clipboard.writeText(text).then(() => { setCopied(id); setTimeout(() => setCopied(null), 2000); });
};
// ── Download ─────────────────────────────────────────────────────────────
const downloadAnswer = (msg) => {
const blob = new Blob([msg.text], { type: "text/plain" });
const a = document.createElement("a"); a.href = URL.createObjectURL(blob);
a.download = `answer-${msg.subject}-${new Date(msg.ts).toLocaleDateString()}.txt`; a.click();
};
// ── Mock Google Login ─────────────────────────────────────────────────────
const googleLogin = () => {
const mockUser = { name: "Student", email: "student@gmail.com", avatar: "https://ui-avatars.com/api/?name=Student&background=6366f1&color=fff&size=80" };
setUser(mockUser);
};
// ── Theme tokens ──────────────────────────────────────────────────────────
const t = {
bg: dark ? "#0f172a" : "#f8fafc",
card: dark ? "#1e293b" : "#ffffff",
border: dark ? "#334155" : "#e2e8f0",
text: dark ? "#f1f5f9" : "#0f172a",
muted: dark ? "#94a3b8" : "#64748b",
inputBg: dark ? "#0f172a" : "#f8fafc",
navBg: dark ? "#1e293b" : "#ffffff",
userBubble: dark ? "#312e81" : "#ede9fe",
userText: dark ? "#c7d2fe" : "#3730a3",
};
// ────────────────────────────────────────────────────────────────────────
// PAGES
// ────────────────────────────────────────────────────────────────────────
// ── HOME ─────────────────────────────────────────────────────────────────
const HomePage = () => (
{txt}
); } // ── Step line: "Step 1:", "Step1:", "1.", numbered const stepMatch = trimmed.match(/^(?:Step\s*(\d+)[:\.]?\s*|(\d+)\.\s+)(.*)$/i); if (stepMatch) { const num = stepMatch[1] || stepMatch[2]; const rest = stepMatch[3]; const color = STEP_COLORS[(parseInt(num) - 1) % STEP_COLORS.length]; stepCount++; return (
Step{num}:
);
}
// ── Final Answer line
if (/^(the\s+)?answer\s+is/i.test(trimmed) || trimmed.toLowerCase().startsWith("final answer") || trimmed.toLowerCase().startsWith("∴") || trimmed.toLowerCase().startsWith("therefore")) {
return (
✅
);
}
// ── Bullet point
if (trimmed.startsWith("- ") || trimmed.startsWith("• ")) {
return (
•
);
}
// ── Bold-only line (section label)
if (trimmed.startsWith("**") && trimmed.endsWith("**")) {
return (
{trimmed.slice(2, -2)}
); } // ── Pro Tip if (/^(pro\s*tip|tip|note)[:\s]/i.test(trimmed)) { return (
๐ก
);
}
// ── Default plain text
return (
{/* Hero */}
{/* Stats strip */}
{/* Choose Subject */}
{/* Top bar */}
{/* Hero text */}
๐
StudyAI
✨
{lang === "hi" ? "AI Study Helper" : "AI Study Helper"}
{lang === "hi" ? "เคोเค เคญी เคธเคตाเคฒ เคชूเคो — เคคुเคฐंเคค เคเคตाเคฌ เคชाเค" : "Ask any question — get instant step-by-step answers"}
{["๐ท Photo", "๐ผ Image", "✍️ Type"].map(t2 => (
{t2}
))}
{[["100%", lang === "hi" ? "เคฎुเคซ्เคค" : "Free"], ["4", lang === "hi" ? "เคตिเคทเคฏ" : "Subjects"], ["2", lang === "hi" ? "เคญाเคทा" : "Languages"]].map(([v, l]) => (
))}
{v}
{l}
<