API Status: Checking...

StudyMate - הפיכת הלמידה לחכמה יותר

העלה חומרי לימוד וקבל סיכומים, מבחנים וכרטיסיות לימוד - הכל אוטומטי!

יצירת פרויקט חדש

מה תרצו לקבל?

חומרי הלימוד שלי

עדיין אין סיכומים. העלה קבצים כדי להתחיל!

עדיין אין כרטיסיות. העלה קבצים כדי להתחיל!

עדיין אין מבחנים. העלה קבצים כדי להתחיל!

/* DeepStudy Pro MAX — Luxe UI * Same powerhouse features, premium UI polish for DeepSite. * Env: OPENAI_API_KEY, OPENAI_MODEL, JWT_SECRET, SMTP_*, APP_BASE_URL */ require('dotenv').config(); const path=require('path'),fs=require('fs'),os=require('os'); const express=require('express'),cors=require('cors'),bodyParser=require('body-parser'); const multer=require('multer'),pdfParse=require('pdf-parse'),mammoth=require('mammoth'); const {v4:uuidv4}=require('uuid'); const helmet=require('helmet'); const rateLimit=require('express-rate-limit'); const cookieParser=require('cookie-parser'); const nodemailer=require('nodemailer'); const bcrypt=require('bcryptjs'); const jwt=require('jsonwebtoken'); const OpenAI=require('openai'); const app=express(); app.use(helmet({contentSecurityPolicy:false})); app.use(cors({origin:true,credentials:true})); app.use(cookieParser()); app.use(bodyParser.json({limit:'16mb'})); app.use(rateLimit({windowMs:60_000,max:180})); const PORT=process.env.PORT||3000; const APP_BASE_URL=process.env.APP_BASE_URL||`http://localhost:${PORT}`; const JWT_SECRET=process.env.JWT_SECRET||'dev_change_me'; const DATA_DIR=path.join(process.cwd(),'data'); const USERS_PATH=path.join(DATA_DIR,'users.json'); const PACKS_PATH=path.join(DATA_DIR,'packs.json'); if(!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR); if(!fs.existsSync(USERS_PATH)) fs.writeFileSync(USERS_PATH,JSON.stringify([])); if(!fs.existsSync(PACKS_PATH)) fs.writeFileSync(PACKS_PATH,JSON.stringify([])); const readJSON=p=>{try{return JSON.parse(fs.readFileSync(p,'utf8'));}catch{return[];}}; const writeJSON=(p,v)=>fs.writeFileSync(p,JSON.stringify(v,null,2)); const readUsers=()=>readJSON(USERS_PATH), writeUsers=a=>writeJSON(USERS_PATH,a); const readPacks=()=>readJSON(PACKS_PATH), writePacks=a=>writeJSON(PACKS_PATH,a); let transporter=null; if(process.env.SMTP_HOST){ transporter=nodemailer.createTransport({host:process.env.SMTP_HOST,port:Number(process.env.SMTP_PORT||587),secure:false,auth:{user:process.env.SMTP_USER,pass:process.env.SMTP_PASS}}); } const openaiConfigured=!!process.env.OPENAI_API_KEY; const openaiClient=openaiConfigured? new OpenAI.OpenAI({apiKey:process.env.OPENAI_API_KEY}):null; const MODEL=process.env.OPENAI_MODEL||'gpt-4o-mini'; const sanitize=s=>(s||'').toString().replace(/\u0000/g,'').trim(); const safeParseJSON=s=>{try{return JSON.parse(s);}catch{return null;}}; const signToken=(payload,exp='7d')=>jwt.sign(payload,JWT_SECRET,{expiresIn:exp}); const verifyToken=t=>{try{return jwt.verify(t,JWT_SECRET);}catch{return null;}}; function authRequired(req,res,next){const t=req.cookies?.dsp_auth; if(!t) return res.status(401).json({ok:false,error:'נדרשת התחברות'}); const dec=verifyToken(t); if(!dec) return res.status(401).json({ok:false,error:'אסימון לא תקף'}); req.user=dec; next();} const upload=multer({dest:path.join(os.tmpdir(),'deepstudy_uploads'),limits:{fileSize:32*1024*1024}}); async function extractText(filePath,originalName){ const ext=(path.extname(originalName||'').toLowerCase()||'').slice(1); if(ext==='pdf'){const d=await pdfParse(fs.readFileSync(filePath)); return sanitize(d.text);} if(ext==='docx'){const {value}=await mammoth.extractRawText({path:filePath}); return sanitize(value);} if(ext==='txt'||ext===''){return sanitize(fs.readFileSync(filePath,'utf8'));} throw new Error(`פורמט לא נתמך: ${ext} (pdf/docx/txt)`); } function chunkText(str,targetTokens=1200,overlapTokens=120){ const txt=sanitize(str); const charsPerTok=3; const chunkChars=targetTokens*charsPerTok; const overlapChars=overlapTokens*charsPerTok; const out=[]; let i=0,idx=1; while(i=txt.length) break;} return out; } async function llm(messages,system){ if(!openaiConfigured){const last=messages[messages.length-1]?.content||''; return `🔁 מצב דמו:\n${last.slice(0,600)}\n\n[הדגמה]`;} const r=await openaiClient.chat.completions.create({model:MODEL,temperature:0.35,messages:[{role:'system',content:system||'You are a kind Hebrew school tutor.'},...messages]}); return r.choices?.[0]?.message?.content?.trim()||''; } const mapPrompt=(goal,sid,text)=>[{role:'user',content: `מקטע "${sid}". מטרה: ${sanitize(goal)||'הבנה + הכנה לבוחן'}. """ ${text} """ צור תקציר (5–8 משפטים), נקודות מפתח, ומושגים. Markdown: ## ${sid} ### תקציר - ... ### נקודות מפתח - ... ### מושגים - מושג | הסבר קצר`}]; function reducePrompt(goal,mapped,merged){return[{role:'user',content: `אחד את סיכומי המקטעים ל"חבילת לימוד" בעברית עם [Si]. מטרה: ${sanitize(goal)||'הבנה + תרגול'}. סיכומים: """ ${mapped} """ (להקשר בלבד): """ ${merged.slice(0,20000)} """ הפק Markdown: # סיכום (8–12 משפטים, עם [Si]) # נקודות מפתח (בולטים + [Si]) # מילון מושגים (טבלה: מושג | הסבר קצר | מקטעים) # כרטיסיות (8–14): שאלה → תשובה קצרה # מבחן לדוגמה (10–14) בסוף הוסף JSON תקין: {"answerKey":[{"qid":"Q1","correct":"B","explanation":"..."}, ...]}`}];} async function buildStudyPack(goal,merged){ const chunks=chunkText(merged,1200,120); if(!chunks.length){return{studyPackMarkdown:'# סיכום\nדמו קצר.',answerKey:{answerKey:[]},sections:[]};} const mapped=[]; for(const ch of chunks) mapped.push(await llm(mapPrompt(goal,ch.id,ch.text))); const mappedMarkdown=mapped.join('\n\n'); const reduced=await llm(reducePrompt(goal,mappedMarkdown,merged)); let answerKey={answerKey:[]}; const m=reduced.match(/\{[\s\S]*\}/g)||[]; for(let i=m.length-1;i>=0;i--){const c=safeParseJSON(m[i]); if(c?.answerKey){answerKey=c;break;}} return{studyPackMarkdown:reduced,answerKey,sections:chunks.map(c=>c.id)}; } // ---------- AUTH ---------- app.post('/api/auth/signup',async(req,res)=>{ try{ const email=sanitize(req.body.email).toLowerCase(), pw=sanitize(req.body.password); if(!email||!pw||pw.length<6) return res.status(400).json({ok:false,error:'אימייל/סיסמה לא תקינים'}); const users=readUsers(); if(users.find(u=>u.email===email)) return res.status(409).json({ok:false,error:'אימייל קיים'}); const hash=await bcrypt.hash(pw,10); const user={id:uuidv4(),email,passwordHash:hash,createdAt:Date.now(),emailVerified:false}; users.push(user); writeUsers(users); if(transporter){ const t=signToken({action:'verify',uid:user.id},'2d'); const link=`${APP_BASE_URL}/verify?token=${t}`; await transporter.sendMail({from:process.env.SMTP_FROM||'DeepStudy Pro ',to:email,subject:'ברוכה הבאה! אשרי את המייל 🎉', html:`

ברוכה הבאה

לאימות החשבון:

לחצי כאן

`}); } const token=signToken({uid:user.id,email:user.email}); res.cookie('dsp_auth',token,{httpOnly:true,sameSite:'lax',maxAge:7*24*3600*1000}); res.json({ok:true,user:{id:user.id,email:user.email,emailVerified:user.emailVerified},info:transporter?'נשלח מייל אימות':'דמו: אין SMTP'}); }catch(e){console.error(e);res.status(500).json({ok:false,error:'שגיאה בהרשמה'});} }); app.post('/api/auth/login',async(req,res)=>{ try{ const email=sanitize(req.body.email).toLowerCase(), pw=sanitize(req.body.password); const users=readUsers(); const u=users.find(x=>x.email===email); if(!u) return res.status(401).json({ok:false,error:'שגוי'}); const ok=await bcrypt.compare(pw,u.passwordHash); if(!ok) return res.status(401).json({ok:false,error:'שגוי'}); const token=signToken({uid:u.id,email:u.email}); res.cookie('dsp_auth',token,{httpOnly:true,sameSite:'lax',maxAge:7*24*3600*1000}); res.json({ok:true,user:{id:u.id,email:u.email,emailVerified:u.emailVerified}}); }catch(e){console.error(e);res.status(500).json({ok:false,error:'שגיאה בהתחברות'});} }); app.post('/api/auth/logout',(req,res)=>{res.clearCookie('dsp_auth');res.json({ok:true});}); app.get('/api/auth/me',(req,res)=>{const t=req.cookies?.dsp_auth; const dec=t&&verifyToken(t); if(!dec) return res.json({ok:false}); const u=readUsers().find(x=>x.id===dec.uid); if(!u) return res.json({ok:false}); res.json({ok:true,user:{id:u.id,email:u.email,emailVerified:u.emailVerified}}); }); app.get('/api/auth/verify',(req,res)=>{const t=sanitize(req.query.token); const d=verifyToken(t); if(!d||d.action!=='verify') return res.status(400).json({ok:false,error:'טוקן לא תקף'}); const users=readUsers(); const u=users.find(x=>x.id===d.uid); if(!u) return res.status(400).json({ok:false,error:'לא קיים'}); if(!u.emailVerified){u.emailVerified=true; writeUsers(users);} res.json({ok:true,verified:true}); }); app.post('/api/auth/request-reset',async(req,res)=>{const email=sanitize(req.body.email).toLowerCase(); const u=readUsers().find(x=>x.email===email); if(u&&transporter){const t=signToken({action:'reset',uid:u.id},'2h'); const link=`${APP_BASE_URL}/reset?token=${t}`; await transporter.sendMail({from:process.env.SMTP_FROM||'DeepStudy Pro ',to:email,subject:'איפוס סיסמה',html:`

איפוס

קישור לאיפוס
`});} res.json({ok:true}); }); app.post('/api/auth/reset',async(req,res)=>{const {token,password}=req.body||{}; const d=verifyToken(sanitize(token)); if(!d||d.action!=='reset') return res.status(400).json({ok:false,error:'טוקן לא תקף'}); if(!password||String(password).length<6) return res.status(400).json({ok:false,error:'סיסמה קצרה'}); const users=readUsers(); const u=users.find(x=>x.id===d.uid); if(!u) return res.status(400).json({ok:false,error:'לא נמצא'}); u.passwordHash=await bcrypt.hash(String(password),10); writeUsers(users); res.json({ok:true}); }); // ---------- CORE ---------- app.get('/api/health',(_req,res)=>res.json({ok:true,model:openaiConfigured?MODEL:'DEMO',smtp:!!transporter})); app.post('/api/study-pack',authRequired,upload.array('files',6),async(req,res)=>{ const cleanup=()=> (req.files||[]).forEach(f=>fs.existsSync(f.path)&&fs.unlinkSync(f.path)); try{ const goal=sanitize(req.body.goal).slice(0,600); let merged=''; if(req.files?.length){for(const f of req.files){merged+=`\n\n===== ${f.originalname} =====\n${await extractText(f.path,f.originalname)}\n`;}} const raw=sanitize(req.body.text||''); if(raw) merged+=`\n\n===== Pasted Text =====\n${raw}\n`; if(!merged.trim()) merged='דמו: אנרגיה, כוח, תאוצה, תנע ושימור אנרגיה.'; const pack=await buildStudyPack(goal,merged); const packs=readPacks(); const id=uuidv4(); packs.push({id,uid:req.user.uid,goal,createdAt:Date.now(),markdown:pack.studyPackMarkdown,answerKey:pack.answerKey,sections:pack.sections}); writePacks(packs); res.json({ok:true,id,...pack}); }catch(e){console.error(e);res.status(500).json({ok:false,error:'שגיאה ביצירת חבילת לימוד',details:String(e.message||e)});} finally{cleanup();} }); app.get('/api/packs',authRequired,(req,res)=>{const p=readPacks().filter(x=>x.uid===req.user.uid).sort((a,b)=>b.createdAt-a.createdAt); res.json({ok:true,packs:p.map(x=>({id:x.id,goal:x.goal,createdAt:x.createdAt}))}); }); app.get('/api/packs/:id',authRequired,(req,res)=>{const p=readPacks().find(x=>x.id===req.params.id&&x.uid===req.user.uid); if(!p) return res.status(404).json({ok:false,error:'לא נמצא'}); res.json({ok:true,pack:p});}); app.delete('/api/packs/:id',authRequired,(req,res)=>{const ps=readPacks(); const i=ps.findIndex(x=>x.id===req.params.id&&x.uid===req.user.uid); if(i===-1) return res.status(404).json({ok:false,error:'לא נמצא'}); ps.splice(i,1); writePacks(ps); res.json({ok:true});}); app.post('/api/grade',authRequired,async(req,res)=>{try{ const {questions,userAnswers,answerKey}=req.body||{}; if(!questions||!userAnswers||!answerKey) return res.status(400).json({ok:false,error:'חסר מידע'}); let out; if(!openaiConfigured){const per=Object.keys(userAnswers).map((qid,i)=>({qid,correct:i%2===0,feedback:i%2===0?'נכון!':'בדקי שוב'})); const c=per.filter(p=>p.correct).length; out={perQuestion:per,score:{correct:c,total:per.length||1,percent:per.length?Math.round(100*c/per.length):0}};} else{const resp=await llm([{role:'user',content:`You are a deterministic grader. Return STRICT JSON: {"perQuestion":[{"qid":"Q1","correct":true,"feedback":"..."}], "score":{"correct":N,"total":T,"percent":P}}`},{role:'user',content:JSON.stringify({questions,userAnswers,answerKey})}],'You are a JSON-only grader.'); const parsed=safeParseJSON(resp); out=parsed?.perQuestion&&parsed?.score?parsed:{perQuestion:[],score:{correct:0,total:0,percent:0}};} res.json({ok:true,result:out}); }catch(e){console.error(e);res.status(500).json({ok:false,error:'שגיאה בבדיקה'});} }); app.get('/api/chat/stream',authRequired,async(req,res)=>{ res.set({'Cache-Control':'no-cache','Content-Type':'text/event-stream',Connection:'keep-alive'}); res.flushHeaders(); const history=safeParseJSON(req.query.history||'[]')||[]; const message=sanitize(req.query.message||''); const msgs=[]; for(const m of history) msgs.push({role:m.role==='assistant'?'assistant':'user',content:String(m.content||'')}); msgs.push({role:'user',content:message}); try{const reply=await llm(msgs,'You are a friendly Hebrew tutor. Keep it clear, stepwise, with one small example. Avoid unsafe content.'); const parts=reply.match(/.{1,60}/g)||[reply]; for(const p of parts){res.write(`data: ${JSON.stringify({chunk:p})}\n\n`); await new Promise(r=>setTimeout(r,14));} res.write(`data: ${JSON.stringify({done:true})}\n\n`); res.end(); }catch(e){res.write(`data: ${JSON.stringify({error:'שגיאת צ׳אט'})}\n\n`); res.end();} }); // ---------- AUX PAGES (verify/reset) ---------- app.get('/verify',(_req,res)=>{res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`אימות
מאמתים…
`);}); app.get('/reset',(_req,res)=>{res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`איפוס סיסמה

איפוס סיסמה

`);}); // ---------- MAIN (Luxe UI) ---------- app.get('/',(_req,res)=>{ res.setHeader('Content-Type','text/html; charset=utf-8'); res.end(` DeepStudy Pro MAX — Luxe

DeepStudy Pro MAX

1) יצירת חבילת לימוד

בדיקת מבחן

הזיני JSON של שאלות/תשובות או השתמשי במפתח שנוצר.

2) צ׳אט מורה + דשבורד

טיפ: ניקוי היסטוריה • קיצור מחזיר הודעה אחרונה

החבילות שלי

`); }); if(!openaiConfigured) console.warn('⚠️ אין OPENAI_API_KEY — מצב דמו זמין (הכול עדיין עובד).'); if(!transporter) console.warn('⚠️ אין SMTP — אימות/שחזור יעבדו חלקית (UI+API), ללא שליחה אמיתית.'); app.listen(PORT,()=>console.log('DeepStudy Pro MAX — Luxe UI @ '+APP_BASE_URL)); npm i express multer openai pdf-parse mammoth cors dotenv uuid body-parser helmet express-rate-limit cookie-parser nodemailer bcryptjs jsonwebtoken