כלי HTML ליצירה אוטומטית של שלוחות הדף היומי (כל מסכתות הש״ס לפי סדרים) באמצעות ה־API
-
שלום לכולם,
בניתי כלי קטן בקובץ HTML באמצעות ה AI אחד שיוצר אוטומטית, דרך ה־API של ימות המשיח, מבנה שלוחות מלא לכל מסכתות הדף היומי, כולל קבצי הקראה (TTS). אין צורך בהתקנה: פותחים את הקובץ בדפדפן, מדביקים API Token ולוחצים.
מה הכלי יוצר:
שישה סדרים: זרעים, מועד, נשים, נזיקין, קדשים, טהרות
כל סדר הוא שלוחת תפריט עם M0000.tts אוטומטי ("למסכת ביצה הקישו 1…")
כל מסכת היא שלוחת השמעת קבצים (שלוחות 1–9), עם שם השלוחה ואפשרות ל־M0000.tts משלה
בשלוחה 0 יש תפריט עם שאר המסכתות באותו סדר. בתפריט האחרון מוקרא "זוהי השלוחה האחרונה בסדר X"
תפריט ראשי עם בחירת הסדראפשרויות הגדרה:
סדר המסכתות בתוך כל סדר: לפי סדר הש״ס, לפי א־ב או לפי מספר הדפים
כמה מסכתות בכל תפריט (עד 9)
בחירה אילו סדרים ליצור
עריכה מלאה של נוסח ההקראה
הגדרות ברירת מחדל (key=value) שנשלחות אוטומטית לכל שלוחות ההשמעה
תצוגה מקדימה של כל השלוחות והקבצים לפני היצירה, התקדמות בזמן אמת, ו"נסה שוב" לפעולות שנכשלואיך זה עובד טכנית:
ההתחברות היא באמצעות API KEY (טוקן קבוע מממשק חומת האש). הכלי משתמש בפקודות UpdateExtension ליצירה ועדכון שלוחות, ב־UploadTextFile להעלאת קבצי TTS, וב־GetSession לבדיקת חיבור. UpdateExtension מעדכן רק את מה שנשלח, ולכן לא נמחק תוכן קיים בשלוחות. קבצי M0000.tts קיימים באותו נתיב יידרסו.
חשוב לדעת:
הטוקן נשאר בדפדפן ולא נשמר ולא נשלח לשום מקום מלבד שרת ימות המשיח.
אם הדפדפן חוסם את הקריאות (CORS), אפשר לעבור לשליחת הטוקן בכותרת authorization. אפשר גם להמיר את זה לסקריפט שרץ בשרת.
סוג השלוחה ברירת המחדל הוא playfile. שמות הגדרות נוספות כדאי להעתיק מ־ext.ini של שלוחה שהגדרתם ידנית.
מומלץ להריץ קודם על שלוחת ניסיון.הקובץ מצורף. אשמח לשמוע הערות והצעות לשיפור.
<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>בונה שלוחות דף יומי – ימות המשיח</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=Frank+Ruhl+Libre:wght@700;900&family=Heebo:wght@400;500;700&display=swap" rel="stylesheet"> <style> :root{ --bg:#f3f5f7; --card:#ffffff; --ink:#1b2a38; --muted:#5c6b79; --line:#d5dce3; --accent:#0e6a6a; --accent-ink:#ffffff; --warn:#a15c00; --bad:#b42318; --ok:#12733f; --code:#eef2f5; } @media (prefers-color-scheme:dark){ :root{--bg:#10171e; --card:#18222c; --ink:#e6ecf1; --muted:#97a6b4; --line:#2b3a47; --accent:#4fc3c3; --accent-ink:#06201f; --warn:#f0b35a; --bad:#ff8a7a; --ok:#5fd18c; --code:#0f1720;} } *{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.6 Heebo,system-ui,Arial,sans-serif} header{padding:28px 20px 8px;max-width:980px;margin:0 auto} h1{font:900 34px/1.2 "Frank Ruhl Libre",serif;margin:0 0 6px} header p{margin:0;color:var(--muted)} main{max-width:980px;margin:0 auto;padding:12px 20px 60px;display:grid;gap:16px} section{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:18px 20px} h2{font-size:18px;margin:0 0 12px} .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px 16px} label{display:block;font-size:14px;color:var(--muted);margin-bottom:4px} input[type=text],input[type=password],input[type=number],select,textarea{ width:100%;padding:8px 10px;border:1px solid var(--line);border-radius:6px;background:var(--bg);color:var(--ink);font:inherit} textarea{min-height:70px;resize:vertical} input[dir=ltr],textarea[dir=ltr]{text-align:left} input:focus-visible,select:focus-visible,textarea:focus-visible,button:focus-visible{outline:2px solid var(--accent);outline-offset:2px} .chk{display:flex;gap:8px;align-items:center;color:var(--ink);font-size:15px;margin:0} .chk input{width:auto} .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center} .sed{display:flex;gap:6px 18px;flex-wrap:wrap} button{font:inherit;font-weight:500;padding:9px 18px;border-radius:6px;border:1px solid var(--line);background:var(--card);color:var(--ink);cursor:pointer} button.primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)} button:disabled{opacity:.5;cursor:not-allowed} .hint{font-size:13px;color:var(--muted);margin-top:6px} .status{font-size:14px;margin-inline-start:6px} .ok{color:var(--ok)} .bad{color:var(--bad)} .warn{color:var(--warn)} table{width:100%;border-collapse:collapse;font-size:14px} th,td{padding:6px 8px;border-bottom:1px solid var(--line);text-align:start;vertical-align:top} th{color:var(--muted);font-weight:500} td.path{direction:ltr;text-align:left;font-family:ui-monospace,Consolas,monospace;font-size:13px;white-space:nowrap} .tts{color:var(--muted);font-size:13px} .wrap{max-height:420px;overflow:auto;border:1px solid var(--line);border-radius:6px} progress{width:100%;height:14px;accent-color:var(--accent)} #log{background:var(--code);border-radius:6px;padding:10px;max-height:260px;overflow:auto;font:13px/1.5 ui-monospace,Consolas,monospace;direction:ltr;text-align:left;white-space:pre-wrap} .pill{display:inline-block;background:var(--code);border-radius:99px;padding:1px 10px;font-size:13px;color:var(--muted)} </style> </head> <body> <header> <h1>בונה שלוחות הש״ס</h1> <p>יוצר אוטומטית שלוחות השמעת קבצים לכל המסכתות, מחולקות לפי סדרים, עם קבצי הקראה (TTS) – דרך ה־API של ימות המשיח.</p> </header> <main> <section> <h2>חיבור למערכת</h2> <div class="grid"> <div><label for="token">API Token (מפתח קבוע מחומת האש)</label> <input id="token" type="password" dir="ltr" autocomplete="off" placeholder="הדבק כאן את הטוקן"></div> <div><label for="base">שלוחת הבסיס (למשל 7, או ריק לשלוחה הראשית)</label> <input id="base" type="text" dir="ltr" value="" placeholder="ריק = שלוחה ראשית"></div> <div><label for="server">שרת</label> <select id="server"> <option value="https://www.call2all.co.il/ym/api/">www.call2all.co.il</option> <option value="https://private.call2all.co.il/ym/api/">private.call2all.co.il</option> </select></div> <div><label for="auth">אופן שליחת הטוקן</label> <select id="auth"> <option value="param">פרמטר token (תואם דפדפן)</option> <option value="header">כותרת authorization (מומלץ בשרת)</option> </select></div> </div> <div class="row" style="margin-top:12px"> <button id="btnTest" type="button">בדיקת חיבור</button><span id="testStatus" class="status"></span> </div> <p class="hint">הטוקן נשאר בדפדפן בלבד ולא נשמר. טוקן מסוג שם־משתמש:סיסמה דורש אימות דו־שלבי – מומלץ להשתמש ב־API KEY.</p> </section> <section> <h2>מבנה וסדר</h2> <div class="grid"> <div><label for="order">סדר המסכתות בתוך כל סדר</label> <select id="order"> <option value="shas">לפי סדר הש״ס</option> <option value="alpha">לפי א־ב</option> <option value="pages">לפי מספר הדפים</option> </select></div> <div><label for="pageSize">מסכתות בכל תפריט (עד 9, השאר ב־0)</label> <input id="pageSize" type="number" min="2" max="9" value="9"></div> </div> <div style="margin-top:12px"><label>סדרים שייווצרו</label><div class="sed" id="sedBox"></div></div> <div class="grid" style="margin-top:12px"> <label class="chk"><input type="checkbox" id="emoji" checked> אימוג׳י בשם שלוחת הסדר</label> <label class="chk"><input type="checkbox" id="baseMenu" checked> הגדר את שלוחת הבסיס כתפריט (כשלא ראשית)</label> <label class="chk"><input type="checkbox" id="tractIntro" checked> קובץ M0000.tts בכל מסכת</label> <label class="chk"><input type="checkbox" id="backLine" checked> הוראת חזרה (כוכבית) בתפריטים</label> </div> <p class="hint">כל סדר הוא שלוחת תפריט. מסכתות הן שלוחות השמעת קבצים במספרים 1–9. ב־0 נמצא תפריט עם שאר המסכתות של אותו סדר, ובתפריט האחרון נאמר שזו השלוחה האחרונה.</p> </section> <section> <h2>סוגי שלוחות והגדרות ברירת מחדל</h2> <div class="grid"> <div><label for="menuType">סוג שלוחת תפריט</label><input id="menuType" type="text" dir="ltr" value="menu"></div> <div><label for="playType">סוג שלוחת השמעת קבצים</label><input id="playType" type="text" dir="ltr" value="playfile"></div> </div> <div class="grid" style="margin-top:12px"> <div><label for="playExtra">הגדרות נוספות לשלוחות ההשמעה (שורה לכל key=value)</label> <textarea id="playExtra" dir="ltr" placeholder="key=value"></textarea></div> <div><label for="menuExtra">הגדרות נוספות לשלוחות התפריט</label> <textarea id="menuExtra" dir="ltr" placeholder="key=value"></textarea></div> </div> <p class="hint">ההגדרות נשלחות ל־UpdateExtension, שמעדכן רק את מה שנשלח ולא מוחק את שאר ext.ini. את שמות ההגדרות מעתיקים מקובץ ext.ini של שלוחה קיימת שהגדרת כרצונך.</p> </section> <section> <h2>נוסח ההקראה</h2> <div class="grid"> <div><label for="tRootIntro">פתיחת התפריט הראשי</label><input id="tRootIntro" type="text" value="ברוכים הבאים ללימוד הש״ס."></div> <div><label for="tRootItem">שורת סדר בתפריט הראשי</label><input id="tRootItem" type="text" value="לסדר {seder} הקישו {n}."></div> <div><label for="tSedIntro">פתיחת תפריט סדר</label><input id="tSedIntro" type="text" value="סדר {seder}."></div> <div><label for="tSedIntro2">פתיחת תפריט המשך</label><input id="tSedIntro2" type="text" value="המשך סדר {seder}."></div> <div><label for="tItem">שורת מסכת</label><input id="tItem" type="text" value="למסכת {name} הקישו {n}."></div> <div><label for="tMore">יש עוד מסכתות</label><input id="tMore" type="text" value="לשאר המסכתות בסדר זה הקישו 0."></div> <div><label for="tLast">התפריט האחרון בסדר</label><input id="tLast" type="text" value="זוהי השלוחה האחרונה בסדר {seder}."></div> <div><label for="tBack">הוראת חזרה</label><input id="tBack" type="text" value="לחזרה לתפריט הקודם הקישו כוכבית."></div> <div><label for="tTract">פתיחת מסכת</label><input id="tTract" type="text" value="מסכת {name}."></div> </div> <p class="hint">משתנים: {seder} {name} {n} {pages}. הקישו 0 מופיע רק כשיש תפריט המשך.</p> </section> <section> <h2>תצוגה מקדימה <span class="pill" id="count"></span></h2> <div class="wrap"><table><thead><tr><th>נתיב</th><th>שלוחה / קובץ</th></tr></thead><tbody id="preview"></tbody></table></div> </section> <section> <h2>יצירה במערכת</h2> <div class="row"> <button id="btnRun" class="primary" type="button">צור שלוחות</button> <button id="btnStop" type="button" disabled>עצור</button> <button id="btnRetry" type="button" disabled>נסה שוב את הכשלונות</button> <span id="runStatus" class="status"></span> </div> <progress id="prog" value="0" max="1" style="margin-top:12px"></progress> <div id="log" style="margin-top:12px" aria-live="polite"></div> <p class="hint">קובצי M0000.tts קיימים באותו נתיב יידרסו. שלוחות קיימות יעודכנו בלבד, בלי מחיקת תוכן.</p> </section> </main> <script> /* ---------- נתונים (לפי הקובץ שצירפת) ---------- */ const SEDARIM=[ {name:'זרעים',emoji:'🌾'},{name:'מועד',emoji:'📜'},{name:'נשים',emoji:'💍'}, {name:'נזיקין',emoji:'⚖️'},{name:'קדשים',emoji:'🏛️'},{name:'טהרות',emoji:'💧'} ]; // [שם, אינדקס סדר, מספר דפים, דירוג א־ב לפי הקובץ] – לפי סדר הש״ס const RAW=[ ['ברכות',0,63,6],['שבת',1,156,36],['עירובין',1,104,29],['פסחים',1,120,31],['שקלים',1,21,37], ['ראש השנה',1,34,34],['יומא',1,87,13],['סוכה',1,55,26],['ביצה',1,39,4],['תענית',1,30,40], ['מגילה',1,31,16],['מועד קטן',1,28,18],['חגיגה',1,26,10], ['יבמות',2,121,12],['כתובות',2,111,15],['נדרים',2,90,23],['נזיר',2,65,24],['סוטה',2,48,25], ['גיטין',2,89,7],['קידושין',2,81,32], ['בבא קמא',3,118,2],['בבא מציעא',3,118,1],['בבא בתרא',3,175,3],['סנהדרין',3,112,27], ['מכות',3,23,19],['שבועות',3,48,35],['עבודה זרה',3,75,28],['הוריות',3,13,8], ['זבחים',4,119,9],['מנחות',4,109,20],['חולין',4,141,11],['בכורות',4,60,5],['ערכין',4,33,30], ['תמורה',4,33,38],['כריתות',4,27,14],['מעילה',4,20,21],['קינים',4,3,33],['מדות',4,4,17],['תמיד',4,9,39], ['נדה',5,72,22] ]; const TR=RAW.map((r,i)=>({name:r[0],seder:r[1],pages:r[2],alpha:r[3],shas:i+1})); /* ---------- עזרים ---------- */ const $=id=>document.getElementById(id); const fill=(t,m)=>t.replace(/\{(\w+)\}/g,(_,k)=>m[k]!==undefined?m[k]:'{'+k+'}'); const esc=s=>String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); const chunk=(a,n)=>{const o=[];for(let i=0;i<a.length;i+=n)o.push(a.slice(i,i+n));return o}; const join=(...a)=>a.filter(x=>x!=='').join('/'); function sortList(list,mode){ const l=[...list]; if(mode==='alpha')l.sort((a,b)=>a.alpha-b.alpha); else if(mode==='pages')l.sort((a,b)=>a.pages-b.pages||a.shas-b.shas); else l.sort((a,b)=>a.shas-b.shas); return l; } function parseExtra(txt){ const o={}; txt.split(/\r?\n/).forEach(line=>{ const i=line.indexOf('='); if(i>0){const k=line.slice(0,i).trim(),v=line.slice(i+1).trim();if(k)o[k]=v} }); return o; } function normBase(v){ return v.trim().replace(/^ivr2?:/i,'').replace(/^\/+|\/+$/g,''); } /* ---------- בניית התוכנית ---------- */ function opts(){ const seds=[...document.querySelectorAll('#sedBox input:checked')].map(x=>+x.value); const g=id=>$(id).value; return { base:normBase(g('base')), order:g('order'), pageSize:Math.min(9,Math.max(2,parseInt(g('pageSize'))||9)), seds, emoji:$('emoji').checked, baseMenu:$('baseMenu').checked, tractIntro:$('tractIntro').checked, backLine:$('backLine').checked, menuType:g('menuType').trim()||'menu', playType:g('playType').trim()||'playfile', playExtra:parseExtra(g('playExtra')), menuExtra:parseExtra(g('menuExtra')), t:{rootIntro:g('tRootIntro'),rootItem:g('tRootItem'),sedIntro:g('tSedIntro'),sedIntro2:g('tSedIntro2'), item:g('tItem'),more:g('tMore'),last:g('tLast'),back:g('tBack'),tract:g('tTract')} }; } function buildPlan(o){ const ops=[]; const baseDepth=o.base?o.base.split('/').length:0; const depthOf=p=>(p?p.split('/').length:0)-baseDepth; const add=(op)=>{op.depth=depthOf(op.path);ops.push(op)}; if(o.baseMenu&&o.base) add({k:'ext',path:o.base,label:'שלוחת בסיס',params:{type:o.menuType,...o.menuExtra}}); const sed=o.seds.map(i=>({i,...SEDARIM[i]})); const rootText=[o.t.rootIntro,...sed.map((s,k)=>fill(o.t.rootItem,{seder:s.name,n:k+1}))].filter(Boolean).join(' '); add({k:'tts',path:o.base,file:'M0000.tts',text:rootText}); sed.forEach((s,si)=>{ let p=join(o.base,si+1); const list=sortList(TR.filter(t=>t.seder===s.i),o.order); const pages=chunk(list,o.pageSize); pages.forEach((items,pi)=>{ const isLast=pi===pages.length-1; const title=pi===0?((o.emoji?s.emoji+' ':'')+s.name):('המשך '+s.name); add({k:'ext',path:p,label:title,params:{type:o.menuType,title,...o.menuExtra}}); const parts=[fill(pi===0?o.t.sedIntro:o.t.sedIntro2,{seder:s.name})]; items.forEach((t,ti)=>parts.push(fill(o.t.item,{name:t.name,n:ti+1,pages:t.pages,seder:s.name}))); if(!isLast)parts.push(o.t.more); else if(pi>0)parts.push(fill(o.t.last,{seder:s.name})); if(o.backLine)parts.push(o.t.back); add({k:'tts',path:p,file:'M0000.tts',text:parts.filter(Boolean).join(' ')}); items.forEach((t,ti)=>{ const tp=join(p,ti+1); add({k:'ext',path:tp,label:t.name,params:{type:o.playType,title:t.name,...o.playExtra}}); if(o.tractIntro)add({k:'tts',path:tp,file:'M0000.tts',text:fill(o.t.tract,{name:t.name,pages:t.pages,seder:s.name})}); }); p=join(p,0); }); }); return ops; } /* ---------- תצוגה מקדימה ---------- */ function render(){ const ops=buildPlan(opts()); $('count').textContent=ops.length+' פעולות'; $('preview').innerHTML=ops.map(op=>{ const pad=`style="padding-inline-start:${8+op.depth*18}px"`; const path=op.path||'/'; if(op.k==='ext') return `<tr><td class="path">${esc(path)}</td><td ${pad}><b>${esc(op.label)}</b> <span class="tts">(${esc(op.params.type)})</span></td></tr>`; return `<tr><td class="path">${esc(path)}/${op.file}</td><td ${pad}><span class="tts">${esc(op.text)}</span></td></tr>`; }).join(''); } /* ---------- API ---------- */ async function api(cmd,params,method){ const server=$('server').value, token=$('token').value.trim(); const p=new URLSearchParams(params), headers={}; if($('auth').value==='header')headers.authorization=token; else p.set('token',token); let res; if(method==='POST'){ headers['Content-Type']='application/x-www-form-urlencoded;charset=UTF-8'; res=await fetch(server+cmd,{method:'POST',headers,body:p}); }else{ res=await fetch(server+cmd+'?'+p.toString(),{headers}); } return res.json(); } const ivrDir=p=>'ivr2:'+p; const ivrFile=(p,f)=>p?`ivr2:${p}/${f}`:`ivr2:/${f}`; async function doOp(op){ let j; if(op.k==='ext')j=await api('UpdateExtension',{path:ivrDir(op.path),...op.params},'GET'); else j=await api('UploadTextFile',{what:ivrFile(op.path,op.file),contents:op.text},'POST'); if(j.responseStatus!=='OK')throw new Error((j.message||j.exceptionMessage||j.responseStatus)+(j.message==='MFA_REQUIRED'?' – השתמש בטוקן מסוג API KEY':'')); } const opName=op=>op.k==='ext'?`ext ${op.path||'/'} (${op.label})`:`tts ${op.path||''}/${op.file}`; $('btnTest').onclick=async()=>{ const st=$('testStatus'); if(!$('token').value.trim()){st.className='status bad';st.textContent='חסר טוקן';return} st.className='status';st.textContent='בודק…'; try{ const j=await api('GetSession',{},'GET'); if(j.responseStatus==='OK'){st.className='status ok';st.textContent='מחובר: מערכת '+(j.username||'')+(j.name?' – '+j.name:'')} else{st.className='status bad';st.textContent=(j.message||j.responseStatus)} }catch(e){st.className='status bad';st.textContent='לא ניתן להתחבר מהדפדפן (ייתכן חסם CORS או שהקובץ נפתח בלי אינטרנט)'} }; /* ---------- הרצה ---------- */ let stopFlag=false, failed=[]; const log=m=>{const l=$('log');l.textContent+=m+'\n';l.scrollTop=l.scrollHeight}; async function run(ops){ stopFlag=false;failed=[]; $('btnRun').disabled=true;$('btnRetry').disabled=true;$('btnStop').disabled=false; $('prog').max=ops.length;$('prog').value=0; let done=0; for(const op of ops){ if(stopFlag){log('— נעצר ידנית —');break} let err=null; for(let a=0;a<3;a++){ try{await doOp(op);err=null;break}catch(e){err=e;await new Promise(r=>setTimeout(r,500*(a+1)))} } if(err){failed.push(op);log('✗ '+opName(op)+' → '+err.message)} else log('✓ '+opName(op)); $('prog').value=++done; $('runStatus').textContent=`${done} מתוך ${ops.length}`; } $('btnRun').disabled=false;$('btnStop').disabled=true;$('btnRetry').disabled=failed.length===0; $('runStatus').className='status '+(failed.length?'warn':'ok'); $('runStatus').textContent=failed.length?`הסתיים עם ${failed.length} כשלונות`:'הסתיים בהצלחה'; } $('btnRun').onclick=()=>{ if(!$('token').value.trim()){alert('יש להזין API Token');return} const o=opts(); if(!o.seds.length){alert('יש לבחור לפחות סדר אחד');return} const ops=buildPlan(o); if(!confirm(`ייווצרו או יעודכנו ${ops.length} פעולות במערכת${o.base?' תחת שלוחה '+o.base:' בשלוחה הראשית'}.\nקבצי M0000.tts קיימים יידרסו. להמשיך?`))return; $('log').textContent=''; run(ops); }; $('btnStop').onclick=()=>{stopFlag=true}; $('btnRetry').onclick=()=>{if(failed.length){$('log').textContent+='— ניסיון חוזר —\n';run([...failed])}}; /* ---------- אתחול ---------- */ $('sedBox').innerHTML=SEDARIM.map((s,i)=>`<label class="chk"><input type="checkbox" value="${i}" checked> ${s.emoji} ${s.name}</label>`).join(''); document.querySelectorAll('input,select,textarea').forEach(el=>{ if(el.id!=='token')el.addEventListener('input',render); }); render(); </script> </body> </html>
עוד סוג של קוד
<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>בונה שלוחות ש"ס אוטומטי - ימות המשיח</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Heebo:wght@300;400;500;600;700;800&family=Frank+Ruhl+Libre:wght@700;800&display=swap" rel="stylesheet"> <style> body { font-family: 'Heebo', sans-serif; } .font-serif-hebrew { font-family: 'Frank Ruhl Libre', serif; } /* Custom scrollbars */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f5f9; } ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #94a3b8; } </style> </head> <body class="bg-slate-50 text-slate-800 min-h-screen flex flex-col"> <header class="bg-gradient-to-r from-blue-900 via-indigo-900 to-slate-900 text-white shadow-lg sticky top-0 z-40"> <div class="max-w-7xl mx-auto px-4 py-4 flex flex-col sm:flex-row justify-between items-center gap-3"> <div class="flex items-center gap-3"> <div class="w-12 h-12 bg-amber-400/20 border border-amber-300/40 rounded-xl flex items-center justify-center text-2xl shadow-inner"> 📖 </div> <div> <h1 class="text-xl sm:text-2xl font-bold font-serif-hebrew tracking-wide text-amber-200">יוצר שלוחות ש"ס למערכות ימות המשיח</h1> <p class="text-xs text-slate-300">הקמת תפריטי סדרים ומסכתות, שלוחות השמעת קבצים וקובצי TTS באופן אוטומטי מלא</p> </div> </div> <div class="flex items-center gap-2"> <span id="apiStatusBadge" class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium bg-slate-800 text-slate-300 border border-slate-700"> <span class="w-2 h-2 rounded-full bg-slate-500"></span> לא מחובר </span> </div> </div> </header> <main class="max-w-7xl mx-auto px-4 py-6 flex-1 w-full grid grid-cols-1 lg:grid-cols-12 gap-6"> <!-- Left / Control Sidebar (4 cols) --> <div class="lg:col-span-4 space-y-6"> <!-- API Connection Box --> <section class="bg-white rounded-2xl shadow-sm border border-slate-200 p-5"> <div class="flex items-center justify-between mb-4 border-b border-slate-100 pb-3"> <h2 class="text-base font-bold text-slate-800 flex items-center gap-2"> <span>🔑</span> פרטי חיבור ל-API </h2> <span class="text-xs text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded font-mono">Yemot IVR</span> </div> <div class="space-y-4"> <div> <label class="block text-xs font-semibold text-slate-600 mb-1">טוקן API (API Token / Key)</label> <input type="password" id="apiTokenInput" placeholder="הזן טוקן קבוע או זמני..." class="w-full text-sm px-3 py-2 rounded-lg border border-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 font-mono"> <p class="text-[11px] text-slate-400 mt-1">טוקן קבוע שהונפק במערכת או שילוב מספר:סיסמה</p> </div> <div> <label class="block text-xs font-semibold text-slate-600 mb-1">נתיב שלוחת הבסיס (Root Path)</label> <div class="flex items-center"> <span class="inline-flex items-center px-3 py-2 rounded-r-lg border border-l-0 border-slate-300 bg-slate-100 text-slate-500 text-sm font-mono dir-ltr">/</span> <input type="text" id="basePathInput" value="1" placeholder="לדוגמה: 1 או 8/1" class="w-full text-sm px-3 py-2 rounded-l-lg border border-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 font-mono"> </div> <p class="text-[11px] text-slate-400 mt-1">באיזו שלוחה יוקם עץ הש"ס (השאר ריק עבור השלוחה הראשית /)</p> </div> <div> <label class="block text-xs font-semibold text-slate-600 mb-1">פרוקסי CORS (לשליחה ישירה מהדפדפן)</label> <select id="corsProxySelect" class="w-full text-sm px-3 py-2 rounded-lg border border-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white"> <option value="direct">ללא פרוקסי (פנייה ישירה ל-call2all)</option> <option value="corsproxy" selected>CORS Proxy ציבורי (corsproxy.io)</option> <option value="allorigins">AllOrigins Proxy</option> <option value="custom">פרוקסי מותאם אישית...</option> </select> <input type="text" id="customProxyInput" placeholder="https://your-proxy.com/?url=" class="hidden mt-2 w-full text-xs px-3 py-1.5 rounded-lg border border-slate-300 font-mono"> </div> <div class="pt-2 flex gap-2"> <button id="testConnectionBtn" type="button" class="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold py-2 px-3 rounded-lg border border-slate-300 transition-colors flex items-center justify-center gap-1.5"> <span>🔌</span> בדוק חיבור </button> <button id="viewSessionBtn" type="button" class="bg-slate-50 hover:bg-slate-100 text-slate-600 text-xs py-2 px-3 rounded-lg border border-slate-200 transition-colors" title="פרטי מערכת ויחידות"> ℹ️ פרטים </button> </div> <div id="connectionMsg" class="hidden text-xs p-2.5 rounded-lg border"></div> </div> </section> <section class="bg-white rounded-2xl shadow-sm border border-slate-200 p-5"> <div class="flex items-center justify-between mb-4 border-b border-slate-100 pb-3"> <h2 class="text-base font-bold text-slate-800 flex items-center gap-2"> <span>⚙️</span> הגדרות שלוחות השמעת קבצים </h2> <span class="text-xs bg-emerald-50 text-emerald-700 px-2 py-0.5 rounded font-mono font-bold">playfile</span> </div> <p class="text-xs text-slate-500 mb-3">הגדרות שיוטמעו בקובץ <code class="bg-slate-100 px-1 py-0.5 rounded text-indigo-700 font-mono">ext.ini</code> של כל שלוחת מסכת:</p> <div class="space-y-3 text-xs"> <div> <label class="font-semibold text-slate-700 block mb-1">סוג שלוחה (type)</label> <input type="text" id="settingType" value="playfile" class="w-full px-2.5 py-1.5 rounded border border-slate-300 bg-slate-50 font-mono" readonly> </div> <div> <label class="font-semibold text-slate-700 block mb-1">סדר השמעת קבצים (playfile_order)</label> <select id="settingOrder" class="w-full px-2.5 py-1.5 rounded border border-slate-300 bg-white"> <option value="asc" selected>מהישן לחדש (סדר עולה 000, 001...)</option> <option value="desc">מהחדש לישן (סדר יורד)</option> </select> </div> <div class="grid grid-cols-2 gap-2"> <div> <label class="font-semibold text-slate-700 block mb-1">השמעה אוטומטית</label> <select id="settingAutoPlay" class="w-full px-2.5 py-1.5 rounded border border-slate-300 bg-white"> <option value="yes" selected>כן (yes)</option> <option value="no">לא (no)</option> </select> </div> <div> <label class="font-semibold text-slate-700 block mb-1">זכירת מיקום מאזין</label> <select id="settingRemember" class="w-full px-2.5 py-1.5 rounded border border-slate-300 bg-white"> <option value="yes" selected>כן (שמירת מיקום)</option> <option value="no">לא (מהתחלה)</option> </select> </div> </div> <div> <label class="font-semibold text-slate-700 block mb-1">שורות הגדרה נוספות (ext.ini)</label> <textarea id="settingExtraIni" rows="3" class="w-full px-2.5 py-1.5 rounded border border-slate-300 font-mono text-[11px]" placeholder="לדוגמה: playfile_end_goto=/ speed=1.0"></textarea> </div> <div class="pt-2"> <label class="font-semibold text-slate-700 block mb-1">תבנית הודעת הקראה למסכת (TTS)</label> <input type="text" id="massechetTtsTemplate" value="מסכת {name}, {pages} דפים" class="w-full px-2.5 py-1.5 rounded border border-slate-300 text-xs"> <p class="text-[10px] text-slate-400 mt-0.5">קובץ M0000.TTS בשלוחת המסכת להכרזת השם</p> </div> </div> </section> <!-- Execution Panel --> <section class="bg-gradient-to-br from-indigo-900 to-slate-900 text-white rounded-2xl p-5 shadow-lg space-y-4"> <h3 class="text-base font-bold text-amber-300 flex items-center gap-2"> <span>🚀</span> יצירה במערכת בפועל </h3> <p class="text-xs text-slate-300 leading-relaxed"> בלחיצה על הכפתור, המערכת תשלח פקודות <span class="text-amber-200 font-mono">UpdateExtension</span> ו-<span class="text-amber-200 font-mono">UploadTextFile</span> לשרתי ימות המשיח, תבנה את 6 הסדרים, כל דפי הניווט (כולל מקש 0 לעוד מסכתות), וכל שלוחות ההשמעה עם קובצי TTS. </p> <div class="flex flex-col gap-2 pt-1"> <button id="startBuildBtn" type="button" class="w-full bg-amber-400 hover:bg-amber-300 text-slate-950 font-bold py-3 px-4 rounded-xl shadow-md transition-all text-sm flex items-center justify-center gap-2"> <span>⚡</span> צור את כל שלוחות הש"ס במערכת </button> <button id="downloadZipBtn" type="button" class="w-full bg-slate-800 hover:bg-slate-700 text-slate-200 py-2 px-3 rounded-lg border border-slate-700 text-xs transition-colors flex items-center justify-center gap-1.5"> <span>💾</span> הורד עותק כקובץ JSON מקומי </button> </div> <!-- Progress bar --> <div id="buildProgressContainer" class="hidden space-y-1.5 pt-2 border-t border-slate-800"> <div class="flex justify-between text-xs text-slate-300"> <span id="progressText">מכין שלוחות...</span> <span id="progressPercent" class="font-mono">0%</span> </div> <div class="w-full bg-slate-800 rounded-full h-2 overflow-hidden border border-slate-700"> <div id="progressBar" class="bg-amber-400 h-full w-0 transition-all duration-150"></div> </div> </div> </section> </div> <div class="lg:col-span-8 space-y-6"> <!-- Top Overview Stats --> <div class="grid grid-cols-2 sm:grid-cols-4 gap-3"> <div class="bg-white rounded-xl border border-slate-200 p-3.5 shadow-sm text-center"> <div class="text-xs text-slate-500 font-medium">סך הכל סדרים</div> <div class="text-2xl font-bold text-indigo-900 mt-1">6</div> <div class="text-[11px] text-slate-400 mt-0.5">מש"ס בבלי</div> </div> <div class="bg-white rounded-xl border border-slate-200 p-3.5 shadow-sm text-center"> <div class="text-xs text-slate-500 font-medium">סך כל המסכתות</div> <div class="text-2xl font-bold text-amber-600 mt-1">40</div> <div class="text-[11px] text-slate-400 mt-0.5">כולל מסכת שקלים</div> </div> <div class="bg-white rounded-xl border border-slate-200 p-3.5 shadow-sm text-center"> <div class="text-xs text-slate-500 font-medium">סה"כ דפים בש"ס</div> <div class="text-2xl font-bold text-emerald-600 mt-1 font-mono">2,711</div> <div class="text-[11px] text-slate-400 mt-0.5">דפי גמרא</div> </div> <div class="bg-white rounded-xl border border-slate-200 p-3.5 shadow-sm text-center"> <div class="text-xs text-slate-500 font-medium">שלוחות שייווצרו</div> <div id="statTotalExtensions" class="text-2xl font-bold text-blue-600 mt-1 font-mono">49</div> <div class="text-[11px] text-slate-400 mt-0.5">סדרים + דפי ניווט + מסכתות</div> </div> </div> <!-- Tabs Header --> <div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden"> <div class="flex border-b border-slate-200 bg-slate-50/70 p-1.5 gap-1.5 text-xs font-semibold"> <button id="tabTreeBtn" class="tab-btn px-4 py-2 rounded-lg bg-white text-indigo-700 shadow-sm border border-slate-200/80"> 🌳 עץ השלוחות והמסכתות המלא </button> <button id="tabSimulatorBtn" class="tab-btn px-4 py-2 rounded-lg text-slate-600 hover:text-slate-900 hover:bg-white/50"> 📞 סימולטור ניווט קולי </button> <button id="tabPayloadBtn" class="tab-btn px-4 py-2 rounded-lg text-slate-600 hover:text-slate-900 hover:bg-white/50"> 📄 קבצי ini ו-TTS שיישלחו </button> <button id="tabLogsBtn" class="tab-btn px-4 py-2 rounded-lg text-slate-600 hover:text-slate-900 hover:bg-white/50"> 📋 יומן פעולות (Logs) </button> </div> <div id="tabTreeContent" class="p-5"> <div class="flex flex-col sm:flex-row justify-between sm:items-center gap-2 mb-4"> <div> <h3 class="font-bold text-slate-800 text-sm">מבנה השלוחות המתוכנן לפי סדר הש"ס</h3> <p class="text-xs text-slate-500">לחיצה על סדר או שלוחה תציג את הפירוט, התפריטים וקובצי ההקראה</p> </div> <div class="flex gap-2"> <button id="expandAllTreeBtn" class="text-xs px-2.5 py-1 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded border border-slate-300">פתח הכל</button> <button id="collapseAllTreeBtn" class="text-xs px-2.5 py-1 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded border border-slate-300">סגור הכל</button> </div> </div> <div id="treeContainer" class="space-y-3 font-mono text-xs"> <!-- Dynamically populated --> </div> </div> <div id="tabSimulatorContent" class="p-5 hidden"> <div class="max-w-xl mx-auto bg-slate-900 text-white rounded-3xl p-6 shadow-2xl border border-slate-800"> <div class="flex items-center justify-between pb-4 border-b border-slate-800 mb-4"> <div class="flex items-center gap-2"> <span class="w-3 h-3 rounded-full bg-emerald-500 animate-pulse"></span> <span class="text-xs font-semibold text-slate-300">סימולטור ימות המשיח בזמן אמת</span> </div> <span id="simCurrentPath" class="text-xs font-mono text-amber-300 bg-slate-800 px-2 py-0.5 rounded">שלוחה ראשית /</span> </div> <!-- Screen: Speech Bubble of IVR TTS --> <div class="bg-slate-800/80 rounded-2xl p-4 border border-slate-700 mb-5 min-h-[120px] flex flex-col justify-between"> <div> <span class="text-[11px] font-bold uppercase tracking-wider text-amber-400 block mb-1">📢 הקראה קולית (M0000.TTS):</span> <p id="simVoiceText" class="text-sm text-slate-100 font-sans leading-relaxed">ברוכים הבאים לש"ס בבלי. לבחירת סדר: הקישו 1 לזרעים, 2 למועד, 3 לנשים, 4 לנזיקין, 5 לקדשים, 6 לטהרות.</p> </div> <div class="mt-3 pt-2 border-t border-slate-700/60 flex items-center justify-between text-[11px] text-slate-400"> <span id="simExtType">סוג: תפריט (menu)</span> <button id="simSpeakAudioBtn" class="text-indigo-300 hover:text-indigo-200 flex items-center gap-1"> <span>🔊</span> השמע בדפדפן (TTS) </button> </div> </div> <!-- Phone Keypad --> <div class="grid grid-cols-3 gap-3 max-w-[280px] mx-auto mb-4"> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="1">1</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="2">2</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="3">3</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="4">4</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="5">5</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="6">6</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="7">7</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="8">8</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="9">9</button> <button class="keypad-btn bg-slate-800/50 hover:bg-slate-800 font-bold text-sm py-3 rounded-xl border border-slate-700 text-slate-400" data-key="*">* (חזרה)</button> <button class="keypad-btn bg-slate-800 hover:bg-slate-700 active:bg-amber-400 active:text-slate-950 font-bold text-lg py-3 rounded-xl border border-slate-700 transition" data-key="0">0</button> <button class="keypad-btn bg-slate-800/50 hover:bg-slate-800 font-bold text-sm py-3 rounded-xl border border-slate-700 text-slate-400" data-key="#"># (אישור)</button> </div> <div class="flex justify-between items-center text-xs pt-2 border-t border-slate-800 text-slate-400"> <button id="simResetBtn" class="hover:text-amber-300">🔄 חזרה לראש התפריט</button> <span>מקש 0 יעביר למסכתות נוספות בסדר</span> </div> </div> </div> <div id="tabPayloadContent" class="p-5 hidden"> <div class="flex justify-between items-center mb-3"> <h3 class="font-bold text-slate-800 text-sm">סקירת קבצים ופקודות שיישלחו למערכת</h3> <span class="text-xs text-slate-500">קבצי ext.ini וקובצי M0000.TTS</span> </div> <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="border border-slate-200 rounded-xl p-3 bg-slate-50 max-h-[460px] overflow-y-auto space-y-1" id="payloadList"> <!-- Dynamically populated list of files --> </div> <div class="md:col-span-2 border border-slate-200 rounded-xl p-4 bg-slate-900 text-slate-100 flex flex-col"> <div class="flex justify-between items-center border-b border-slate-800 pb-2 mb-2 text-xs"> <span id="payloadFileTitle" class="font-mono text-amber-300 font-bold">בחר קובץ מהרשימה</span> <span id="payloadFilePath" class="text-slate-400 font-mono text-[11px]"></span> </div> <pre id="payloadFileContent" class="font-mono text-xs overflow-auto flex-1 p-2 bg-slate-950/60 rounded text-emerald-400 leading-relaxed whitespace-pre-wrap select-all">בחר קובץ מימין לצפייה בתוכנו המדויק</pre> </div> </div> </div> <div id="tabLogsContent" class="p-5 hidden"> <div class="flex justify-between items-center mb-3"> <h3 class="font-bold text-slate-800 text-sm">יומן בקשות ותשובות API</h3> <button id="clearLogsBtn" class="text-xs px-2.5 py-1 bg-slate-100 hover:bg-slate-200 text-slate-600 rounded">נקה יומן</button> </div> <div id="logsConsole" class="bg-slate-950 text-slate-200 rounded-xl p-4 font-mono text-xs h-96 overflow-y-auto space-y-1.5 border border-slate-800"> <div class="text-slate-500">// יומן הפעולות מוכן. לחץ על 'בדוק חיבור' או 'צור שלוחות'.</div> </div> </div> </div> </div> </main> <footer class="bg-slate-100 border-t border-slate-200 text-slate-500 text-xs py-4 text-center"> <div class="max-w-7xl mx-auto px-4"> מערכת ניהול ובניית שלוחות ש"ס למרכזיית ימות המשיח • תואם API 2026 • מודול השמעת קבצים והקראה אוטומטית (TTS) </div> </footer> <script> /* * נתוני ששת סדרי משנה וש"ס בבלי * הסדר המדויק, המספר הסידורי, וכמות הדפים עפ"י הקובץ המצורף (42389.pdf) */ const SEDER_DATA = [ { id: 1, key: "1", name: "זרעים", icon: "🌾", title: "סדר זרעים", masechtot: [ { order: 1, name: "ברכות", pages: 63 } ] }, { id: 2, key: "2", name: "מועד", icon: "📜", title: "סדר מועד", masechtot: [ { order: 2, name: "שבת", pages: 156 }, { order: 3, name: "עירובין", pages: 104 }, { order: 4, name: "פסחים", pages: 120 }, { order: 5, name: "שקלים", pages: 21 }, { order: 6, name: "ראש השנה", pages: 34 }, { order: 7, name: "יומא", pages: 87 }, { order: 8, name: "סוכה", pages: 55 }, { order: 9, name: "ביצה", pages: 39 }, { order: 10, name: "תענית", pages: 30 }, { order: 11, name: "מגילה", pages: 31 }, { order: 12, name: "מועד קטן", pages: 28 }, { order: 13, name: "חגיגה", pages: 26 } ] }, { id: 3, key: "3", name: "נשים", icon: "💍", title: "סדר נשים", masechtot: [ { order: 14, name: "יבמות", pages: 121 }, { order: 15, name: "כתובות", pages: 111 }, { order: 16, name: "נדרים", pages: 90 }, { order: 17, name: "נזיר", pages: 65 }, { order: 18, name: "סוטה", pages: 48 }, { order: 19, name: "גיטין", pages: 89 }, { order: 20, name: "קידושין", pages: 81 } ] }, { id: 4, key: "4", name: "נזיקין", icon: "⚖️", title: "סדר נזיקין", masechtot: [ { order: 21, name: "בבא קמא", pages: 118 }, { order: 22, name: "בבא מציעא", pages: 118 }, { order: 23, name: "בבא בתרא", pages: 175 }, { order: 24, name: "סנהדרין", pages: 112 }, { order: 25, name: "מכות", pages: 23 }, { order: 26, name: "שבועות", pages: 48 }, { order: 27, name: "עבודה זרה", pages: 75 }, { order: 28, name: "הוריות", pages: 13 } ] }, { id: 5, key: "5", name: "קדשים", icon: "🏛️", title: "סדר קדשים", masechtot: [ { order: 29, name: "זבחים", pages: 119 }, { order: 30, name: "מנחות", pages: 109 }, { order: 31, name: "חולין", pages: 141 }, { order: 32, name: "בכורות", pages: 60 }, { order: 33, name: "ערכין", pages: 33 }, { order: 34, name: "תמורה", pages: 33 }, { order: 35, name: "כריתות", pages: 27 }, { order: 36, name: "מעילה", pages: 20 }, { order: 37, name: "קינים", pages: 3 }, { order: 38, name: "תמיד", pages: 9 }, { order: 39, name: "מידות", pages: 4 } ] }, { id: 6, key: "6", name: "טהרות", icon: "✨", title: "סדר טהרות", masechtot: [ { order: 40, name: "נדה", pages: 72 } ] } ]; const State = { apiToken: '', basePath: '1', generatedPlan: [], filesMap: new Map(), // path -> { type, content, desc } simState: { currentPath: '', history: [] } }; /** * פונקציה לבניית מבנה העץ המלא של השלוחות, התפריטים, קבצי ה-ext.ini וקבצי ה-TTS * בהתאם לדרישת הלקוח: * - תפריט סדרים * - בכל סדר תפריט מסכתות מפוצל לדפים (1-9 מסכתות, מקש 0 מעביר לעוד מסכתות) * - בשלוחת 0 האחרונה: הודעה שזו השלוחה האחרונה * - שלוחות מסכת מסוג השמעת קבצים (playfile) עם קובץ TTS */ function generateFullIvrPlan() { const rootPath = (State.basePath.trim() === '' || State.basePath.trim() === '/') ? '' : State.basePath.trim().replace(/^\/+|\/+$/g, ''); const formatPath = (...parts) => parts.filter(p => p !== '' && p !== undefined).join('/'); const plan = []; State.filesMap.clear(); // 1. שלוחת הש"ס הראשית (תפריט סדרים) const rootIvrPath = rootPath === '' ? '' : rootPath; const rootTTS = "ברוכים הבאים לש\"ס בבלי. " + SEDER_DATA.map(s => `לסדר ${s.name} הקישו ${s.key}`).join(", ") + "."; const rootIni = [ "type=menu", "title=ש\"ס בבלי - בחירת סדר", ...SEDER_DATA.map(s => `${s.key}=${formatPath(rootIvrPath, s.key)}`) ].join("\n"); plan.push({ path: rootIvrPath || '/', title: 'ש"ס בבלי - ראשי', type: 'menu', description: 'תפריט בחירת סדר', ini: rootIni, tts: rootTTS, ttsFileName: 'M0000.TTS' }); // רישום קבצים State.filesMap.set(formatPath(rootIvrPath, 'ext.ini') || 'ext.ini', { title: 'הגדרות שלוחה ראשית', content: rootIni }); State.filesMap.set(formatPath(rootIvrPath, 'M0000.TTS') || 'M0000.TTS', { title: 'הקראת תפריט סדרים', content: rootTTS }); // 2. מעבר על כל סדר SEDER_DATA.forEach(seder => { const sederBasePath = formatPath(rootIvrPath, seder.key); const masechtot = seder.masechtot; // אם יש עד 9 מסכתות - דף יחיד. אם יותר - חלוקה לקבוצות של 9 // מקשים 1-9 הם למסכתות, מקש 0 הוא מעבר לקבוצה הבאה (או השלוחה האחרונה) const CHUNK_SIZE = 9; const totalPages = Math.ceil(masechtot.length / CHUNK_SIZE) || 1; for (let pageIdx = 0; pageIdx < totalPages; pageIdx++) { const isFirstPage = (pageIdx === 0); // נתיב הדף: דף ראשון בסדר הוא סדר.קיי, דף הבא הוא תחת שלוחה 0 וכך הלאה let pagePath = sederBasePath; for (let p = 0; p < pageIdx; p++) { pagePath = formatPath(pagePath, '0'); } const startIdx = pageIdx * CHUNK_SIZE; const pageMasechtot = masechtot.slice(startIdx, startIdx + CHUNK_SIZE); const hasNextPage = (pageIdx + 1 < totalPages); // בניית טקסט TTS והגדרות ext.ini לתפריט המסכתות let pageTtsParts = [`סדר ${seder.name}, חלק ${pageIdx + 1}.`]; const pageIniLines = [ "type=menu", `title=סדר ${seder.name} - דף ${pageIdx + 1}` ]; pageMasechtot.forEach((m, idx) => { const digit = (idx + 1).toString(); const masechetPath = formatPath(pagePath, digit); pageTtsParts.push(`למסכת ${m.name} הקישו ${digit}`); pageIniLines.push(`${digit}=${masechetPath}`); // 3. יצירת שלוחת המסכת (השמעת קבצים) const masechetIni = buildPlayfileIni(m.name, seder.name, m.pages); const masechetTts = buildMasechetTts(m.name, m.pages); plan.push({ path: masechetPath, title: `מסכת ${m.name}`, type: 'playfile', description: `שלוחת השמעת קבצים - מסכת ${m.name} (${m.pages} דפים)`, ini: masechetIni, tts: masechetTts, ttsFileName: 'M0000.TTS', sederName: seder.name, masechetName: m.name, pages: m.pages }); State.filesMap.set(formatPath(masechetPath, 'ext.ini'), { title: `הגדרות שלוחה: מסכת ${m.name}`, content: masechetIni }); State.filesMap.set(formatPath(masechetPath, 'M0000.TTS'), { title: `הקראת פתיחה: מסכת ${m.name}`, content: masechetTts }); }); // טיפול במקש 0: if (hasNextPage) { const nextPagePath = formatPath(pagePath, '0'); pageTtsParts.push(`למסכתות נוספות בסדר ${seder.name} הקישו 0`); pageIniLines.push(`0=${nextPagePath}`); } else { // זהו הדף האחרון בסדר זה // שלוחה 0 תודיע שזו השלוחה האחרונה בסדר const lastZeroPath = formatPath(pagePath, '0'); const endSederTts = `זוהי השלוחה האחרונה לסדר ${seder.name}.`; const endSederIni = [ "type=playfile", `title=סיום סדר ${seder.name}`, "playfile_end_goto=.." ].join("\n"); pageTtsParts.push(`למסכתות נוספות בסדר ${seder.name} הקישו 0`); pageIniLines.push(`0=${lastZeroPath}`); plan.push({ path: lastZeroPath, title: `סיום סדר ${seder.name}`, type: 'playfile', description: `שלוחה 0 - סיום סדר ${seder.name}`, ini: endSederIni, tts: endSederTts, ttsFileName: 'M0000.TTS', isLastZero: true }); State.filesMap.set(formatPath(lastZeroPath, 'ext.ini'), { title: `הגדרות שלוחה 0 (סוף סדר ${seder.name})`, content: endSederIni }); State.filesMap.set(formatPath(lastZeroPath, 'M0000.TTS'), { title: `הקראת סוף סדר ${seder.name}`, content: endSederTts }); } const pageTts = pageTtsParts.join(", ") + "."; const pageIni = pageIniLines.join("\n"); plan.push({ path: pagePath, title: `סדר ${seder.name} - תפריט ${pageIdx + 1}`, type: 'menu', description: `תפריט מסכתות ${pageIdx * CHUNK_SIZE + 1} עד ${Math.min((pageIdx + 1) * CHUNK_SIZE, masechtot.length)}`, ini: pageIni, tts: pageTts, ttsFileName: 'M0000.TTS', sederName: seder.name, pageIdx: pageIdx + 1 }); State.filesMap.set(formatPath(pagePath, 'ext.ini'), { title: `הגדרות תפריט: סדר ${seder.name} (חלק ${pageIdx + 1})`, content: pageIni }); State.filesMap.set(formatPath(pagePath, 'M0000.TTS'), { title: `הקראת תפריט: סדר ${seder.name} (חלק ${pageIdx + 1})`, content: pageTts }); } }); State.generatedPlan = plan; return plan; } function buildPlayfileIni(masechetName, sederName, pages) { const order = document.getElementById('settingOrder')?.value || 'asc'; const autoPlay = document.getElementById('settingAutoPlay')?.value || 'yes'; const remember = document.getElementById('settingRemember')?.value || 'yes'; const extra = document.getElementById('settingExtraIni')?.value.trim() || ''; const lines = [ "type=playfile", `title=מסכת ${masechetName} - סדר ${sederName} (${pages} דפים)`, `playfile_order=${order}`, `playfile_auto_play=${autoPlay}`, `playfile_remember_location=${remember}` ]; if (extra) { lines.push(extra); } return lines.join("\n"); } function buildMasechetTts(masechetName, pages) { const template = document.getElementById('massechetTtsTemplate')?.value || 'מסכת {name}, {pages} דפים'; return template.replace('{name}', masechetName).replace('{pages}', pages); } const YemotAPI = { getBaseUrl() { return "https://www.call2all.co.il/ym/api/"; }, wrapUrl(endpoint, params = {}) { const token = State.apiToken.trim(); const fullParams = new URLSearchParams({ token, ...params }); const targetUrl = `${this.getBaseUrl()}${endpoint}?${fullParams.toString()}`; const proxyMode = document.getElementById('corsProxySelect').value; if (proxyMode === 'corsproxy') { return `https://corsproxy.io/?${encodeURIComponent(targetUrl)}`; } else if (proxyMode === 'allorigins') { return `https://api.allorigins.win/raw?url=${encodeURIComponent(targetUrl)}`; } else if (proxyMode === 'custom') { const custom = document.getElementById('customProxyInput').value.trim(); return `${custom}${encodeURIComponent(targetUrl)}`; } return targetUrl; }, async getSession() { const url = this.wrapUrl('GetSession'); log('API', `בודק חיבור עם GetSession...`); const res = await fetch(url); if (!res.ok) throw new Error(`HTTP Error: ${res.status}`); return await res.json(); }, async updateExtension(path, iniObject) { // UpdateExtension?token=...&path=ivr2:1&type=menu... const formattedPath = path.startsWith('ivr2:') ? path : `ivr2:${path === '/' ? '' : path}`; const params = { path: formattedPath, ...iniObject }; const url = this.wrapUrl('UpdateExtension', params); const res = await fetch(url); if (!res.ok) throw new Error(`HTTP Error ${res.status}`); return await res.json(); }, async uploadTextFile(fullIvrPath, contents) { // UploadTextFile?token=...&what=ivr2:1/ext.ini&contents=... const what = fullIvrPath.startsWith('ivr2:') ? fullIvrPath : `ivr2:${fullIvrPath}`; const url = this.wrapUrl('UploadTextFile'); // POST עם FormData כדי למנוע חיתוך טקסט עברי ארוך ב-URL const formData = new FormData(); formData.append('token', State.apiToken.trim()); formData.append('what', what); formData.append('contents', contents); const res = await fetch(url, { method: 'POST', body: formData }); if (!res.ok) throw new Error(`HTTP Error ${res.status}`); return await res.json(); } }; function log(type, msg, data = null) { const logsConsole = document.getElementById('logsConsole'); const time = new Date().toLocaleTimeString('he-IL'); const el = document.createElement('div'); let badgeColor = 'bg-slate-700 text-slate-300'; if (type === 'SUCCESS') badgeColor = 'bg-emerald-900/80 text-emerald-300 border border-emerald-700'; if (type === 'ERROR') badgeColor = 'bg-rose-900/80 text-rose-300 border border-rose-700'; if (type === 'WARN') badgeColor = 'bg-amber-900/80 text-amber-300 border border-amber-700'; if (type === 'API') badgeColor = 'bg-blue-900/80 text-blue-300 border border-blue-700'; el.innerHTML = ` <span class="text-slate-500">[${time}]</span> <span class="px-1.5 py-0.5 rounded text-[10px] font-bold ${badgeColor}">${type}</span> <span class="text-slate-200">${escapeHtml(msg)}</span> ${data ? `<pre class="text-[10px] text-slate-400 mt-0.5 pl-4 overflow-x-auto">${escapeHtml(typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data))}</pre>` : ''} `; logsConsole.appendChild(el); logsConsole.scrollTop = logsConsole.scrollHeight; } function escapeHtml(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } function renderTree() { const container = document.getElementById('treeContainer'); container.innerHTML = ''; const plan = State.generatedPlan; document.getElementById('statTotalExtensions').textContent = plan.length; // קיבוץ לפי סדרים SEDER_DATA.forEach(seder => { const sederItems = plan.filter(p => p.sederName === seder.name || (p.path.includes(`/${seder.key}`) || p.path === `${State.basePath}/${seder.key}`)); const card = document.createElement('div'); card.className = "border border-slate-200 rounded-xl bg-white overflow-hidden shadow-sm"; const header = document.createElement('div'); header.className = "bg-slate-50 px-4 py-3 flex items-center justify-between cursor-pointer hover:bg-slate-100 transition-colors border-b border-slate-100"; header.innerHTML = ` <div class="flex items-center gap-2"> <span class="text-lg">${seder.icon}</span> <span class="font-bold text-slate-800 text-sm font-sans">${seder.title}</span> <span class="bg-indigo-100 text-indigo-800 px-2 py-0.5 rounded-full text-[10px] font-mono font-bold">${seder.masechtot.length} מסכתות</span> </div> <div class="flex items-center gap-3"> <span class="text-slate-400 text-xs font-mono">מקש ${seder.key}</span> <span class="transform transition-transform text-slate-400 text-xs" id="chevron-${seder.id}">▼</span> </div> `; const content = document.createElement('div'); content.className = "p-4 space-y-3 bg-white"; content.id = `seder-content-${seder.id}`; // מסכתות הסדר הזה const masechtotInSeder = plan.filter(p => p.sederName === seder.name && p.type === 'playfile' && !p.isLastZero); const menusInSeder = plan.filter(p => p.sederName === seder.name && p.type === 'menu'); const lastZero = plan.find(p => p.sederName === seder.name && p.isLastZero); let html = ''; // דפי תפריט html += `<div class="mb-3"> <div class="text-[11px] font-bold text-slate-500 uppercase tracking-wider mb-1.5 font-sans">תפריטי ניווט ודפי מקש 0:</div> <div class="grid grid-cols-1 sm:grid-cols-2 gap-2">`; menusInSeder.forEach(m => { html += ` <div class="bg-indigo-50/60 border border-indigo-100 rounded-lg p-2.5"> <div class="flex items-center justify-between font-bold text-indigo-900"> <span>📁 שלוחה ${m.path}</span> <span class="text-[10px] bg-indigo-200/70 text-indigo-800 px-1.5 py-0.2 rounded font-sans">תפריט דף ${m.pageIdx || 1}</span> </div> <div class="text-[11px] text-slate-600 font-sans mt-1"><strong>TTS:</strong> "${m.tts}"</div> </div>`; }); if (lastZero) { html += ` <div class="bg-amber-50/70 border border-amber-200 rounded-lg p-2.5"> <div class="flex items-center justify-between font-bold text-amber-900"> <span>🔔 שלוחה 0 (סוף סדר): ${lastZero.path}</span> <span class="text-[10px] bg-amber-200 text-amber-800 px-1.5 py-0.2 rounded font-sans">הודעת סיום</span> </div> <div class="text-[11px] text-slate-700 font-sans mt-1"><strong>הודעה:</strong> "${lastZero.tts}"</div> </div>`; } html += `</div></div>`; // רשימת המסכתות html += `<div> <div class="text-[11px] font-bold text-slate-500 uppercase tracking-wider mb-1.5 font-sans">שלוחות השמעת קבצים (מסכתות):</div> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">`; masechtotInSeder.forEach(m => { html += ` <div class="border border-slate-200 rounded-lg p-2.5 bg-slate-50 hover:bg-slate-100/80 transition-colors"> <div class="flex justify-between items-start"> <span class="font-bold text-slate-800 font-sans text-xs">מסכת ${m.masechetName}</span> <span class="bg-emerald-100 text-emerald-800 text-[10px] px-1.5 py-0.5 rounded font-mono font-bold">${m.pages} דף</span> </div> <div class="text-[11px] text-slate-500 font-mono mt-1">נתיב: ${m.path}</div> <div class="text-[10px] text-slate-400 font-sans mt-0.5 truncate">TTS: ${m.tts}</div> </div>`; }); html += `</div></div>`; content.innerHTML = html; header.addEventListener('click', () => { content.classList.toggle('hidden'); const chevron = document.getElementById(`chevron-${seder.id}`); chevron.textContent = content.classList.contains('hidden') ? '◀' : '▼'; }); card.appendChild(header); card.appendChild(content); container.appendChild(card); }); renderPayloadsList(); } function renderPayloadsList() { const list = document.getElementById('payloadList'); list.innerHTML = ''; let first = true; State.filesMap.forEach((fileObj, path) => { const item = document.createElement('div'); item.className = "px-2.5 py-2 rounded-lg cursor-pointer text-xs font-mono truncate hover:bg-slate-200/70 border border-transparent transition-colors flex items-center justify-between"; const isIni = path.endsWith('.ini'); const icon = isIni ? '⚙️' : '🔊'; item.innerHTML = ` <span class="truncate" title="${path}">${icon} ${path}</span> <span class="text-[10px] text-slate-400 font-sans">${isIni ? 'ini' : 'TTS'}</span> `; item.addEventListener('click', () => { document.querySelectorAll('#payloadList > div').forEach(el => el.classList.remove('bg-indigo-100', 'border-indigo-300', 'text-indigo-900', 'font-bold')); item.classList.add('bg-indigo-100', 'border-indigo-300', 'text-indigo-900', 'font-bold'); showPayload(path, fileObj); }); list.appendChild(item); if (first) { item.classList.add('bg-indigo-100', 'border-indigo-300', 'text-indigo-900', 'font-bold'); showPayload(path, fileObj); first = false; } }); } function showPayload(path, fileObj) { document.getElementById('payloadFileTitle').textContent = fileObj.title || path; document.getElementById('payloadFilePath').textContent = `ivr2:${path}`; document.getElementById('payloadFileContent').textContent = fileObj.content; } const Simulator = { currentPlanNode: null, init() { this.goToRoot(); this.setupKeypad(); }, goToRoot() { const rootPath = (State.basePath.trim() === '' || State.basePath.trim() === '/') ? '' : State.basePath.trim().replace(/^\/+|\/+$/g, ''); const target = rootPath === '' ? '/' : rootPath; this.navigateTo(target); }, navigateTo(path) { const plan = State.generatedPlan; let node = plan.find(p => p.path === path); if (!node && path === '/') { node = plan.find(p => p.path === '/' || p.path === ''); } if (!node) { this.setScreen(path, "השלוחה אינה קיימת במערכת", "שגיאה"); return; } this.currentPlanNode = node; State.simState.currentPath = node.path; let displayPath = node.path === '' ? '/' : `/${node.path}`; let speech = node.tts; if (node.type === 'playfile') { speech = `שלוחת השמעת קבצים: ${node.title}. (${node.tts}). מתחיל השמעת שיעורים...`; } this.setScreen(displayPath, speech, `סוג: ${node.type} | ${node.title}`); }, setScreen(path, text, type) { document.getElementById('simCurrentPath').textContent = `שלוחה: ${path}`; document.getElementById('simVoiceText').textContent = text; document.getElementById('simExtType').textContent = type; }, pressKey(key) { if (!this.currentPlanNode) return; // כפתור כוכבית לחזרה אחורה if (key === '*') { const parts = this.currentPlanNode.path.split('/').filter(Boolean); if (parts.length > 0) { parts.pop(); const parent = parts.join('/') || '/'; this.navigateTo(parent); } else { this.navigateTo('/'); } return; } // אם זו שלוחת תפריט if (this.currentPlanNode.type === 'menu') { const ini = this.currentPlanNode.ini; const match = ini.split('\n').find(line => line.startsWith(`${key}=`)); if (match) { const targetPath = match.split('=')[1].trim(); this.navigateTo(targetPath); } else { this.setScreen(this.currentPlanNode.path, `הקשה ${key} שגויה. ${this.currentPlanNode.tts}`, `סוג: menu`); } } else if (this.currentPlanNode.type === 'playfile') { if (key === '0') { this.setScreen(this.currentPlanNode.path, `בשלוחת השמעת קבצים לא הוגדר מעבר על מקש 0.`, `סוג: playfile`); } } }, setupKeypad() { document.querySelectorAll('.keypad-btn').forEach(btn => { btn.addEventListener('click', (e) => { const key = btn.getAttribute('data-key'); this.pressKey(key); }); }); document.getElementById('simResetBtn').addEventListener('click', () => { this.goToRoot(); }); // כפתור דיבור TTS של הדפדפן לבדיקת שמיעה document.getElementById('simSpeakAudioBtn').addEventListener('click', () => { const text = document.getElementById('simVoiceText').textContent; if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'he-IL'; utterance.rate = 0.95; window.speechSynthesis.speak(utterance); } else { alert('הדפדפן אינו תומך בהקראת קולית מובנית'); } }); } }; async function startBuildingExtensions() { const token = document.getElementById('apiTokenInput').value.trim(); if (!token) { alert('אנא הזן טוקן API של ימות המשיח כדי לבצע את ההקמה'); document.getElementById('apiTokenInput').focus(); return; } State.apiToken = token; State.basePath = document.getElementById('basePathInput').value.trim(); // רענון התוכנית generateFullIvrPlan(); const plan = State.generatedPlan; if (!confirm(`האם להתחיל בהקמת ${plan.length} שלוחות ש"ס וקבצי TTS במערכת ימות המשיח שלך?`)) { return; } const progressContainer = document.getElementById('buildProgressContainer'); const progressBar = document.getElementById('progressBar'); const progressText = document.getElementById('progressText'); const progressPercent = document.getElementById('progressPercent'); const startBtn = document.getElementById('startBuildBtn'); progressContainer.classList.remove('hidden'); startBtn.disabled = true; startBtn.classList.add('opacity-50', 'cursor-not-allowed'); log('INFO', `מתחיל תהליך הקמה מלא של ${plan.length} שלוחות ש"ס...`); let completed = 0; let errors = 0; for (let i = 0; i < plan.length; i++) { const item = plan[i]; const percent = Math.round(((i + 1) / plan.length) * 100); progressBar.style.width = `${percent}%`; progressPercent.textContent = `${percent}%`; progressText.textContent = `יוצר: ${item.title} (${item.path || '/'})`; try { // שלב 1: העלאת קובץ ext.ini באמצעות UploadTextFile const iniPath = item.path === '/' || item.path === '' ? 'ext.ini' : `${item.path}/ext.ini`; log('API', `מעלה הגדרות: ${iniPath}`); await YemotAPI.uploadTextFile(iniPath, item.ini); // שלב 2: העלאת קובץ M0000.TTS עם טקסט ההקראה האוטומטי const ttsPath = item.path === '/' || item.path === '' ? item.ttsFileName : `${item.path}/${item.ttsFileName}`; log('API', `מעלה קובץ הקראה: ${ttsPath}`); await YemotAPI.uploadTextFile(ttsPath, item.tts); completed++; log('SUCCESS', `הושלמה שלוחה: ${item.title} [${item.path}]`); // השהייה קלה למניעת הצפת ה-API await new Promise(r => setTimeout(r, 200)); } catch (err) { errors++; log('ERROR', `נכשלה הקמת שלוחה ${item.path}: ${err.message}`, err); // ממשיך לשלוחה הבאה } } progressBar.style.width = '100%'; progressPercent.textContent = '100%'; progressText.textContent = 'ההקמה הסתיימה!'; startBtn.disabled = false; startBtn.classList.remove('opacity-50', 'cursor-not-allowed'); if (errors === 0) { log('SUCCESS', `כל ${completed} השלוחות הוקמו בהצלחה מלאה!`); alert(`ההקמה הסתיימה בהצלחה!\nנוצרו ${completed} שלוחות, תפריטי ניווט וקובצי TTS.`); } else { log('WARN', `ההקמה הסתיימה עם ${errors} שגיאות מתוך ${plan.length} שלוחות.`); alert(`ההקמה הסתיימה.\nהצליחו: ${completed}\nנכשלו: ${errors}\nבדוק את לשונית היומן לפרטים.`); } } function initUI() { // Tab switching const tabs = [ { btn: 'tabTreeBtn', content: 'tabTreeContent' }, { btn: 'tabSimulatorBtn', content: 'tabSimulatorContent' }, { btn: 'tabPayloadBtn', content: 'tabPayloadContent' }, { btn: 'tabLogsBtn', content: 'tabLogsContent' } ]; tabs.forEach(t => { document.getElementById(t.btn).addEventListener('click', () => { tabs.forEach(other => { const b = document.getElementById(other.btn); const c = document.getElementById(other.content); if (other.btn === t.btn) { b.className = "tab-btn px-4 py-2 rounded-lg bg-white text-indigo-700 shadow-sm border border-slate-200/80 font-bold"; c.classList.remove('hidden'); } else { b.className = "tab-btn px-4 py-2 rounded-lg text-slate-600 hover:text-slate-900 hover:bg-white/50"; c.classList.add('hidden'); } }); }); }); // Expand / Collapse all document.getElementById('expandAllTreeBtn').addEventListener('click', () => { SEDER_DATA.forEach(s => { document.getElementById(`seder-content-${s.id}`).classList.remove('hidden'); document.getElementById(`chevron-${s.id}`).textContent = '▼'; }); }); document.getElementById('collapseAllTreeBtn').addEventListener('click', () => { SEDER_DATA.forEach(s => { document.getElementById(`seder-content-${s.id}`).classList.add('hidden'); document.getElementById(`chevron-${s.id}`).textContent = '◀'; }); }); // Proxy custom input toggle document.getElementById('corsProxySelect').addEventListener('change', (e) => { const customInput = document.getElementById('customProxyInput'); if (e.target.value === 'custom') { customInput.classList.remove('hidden'); } else { customInput.classList.add('hidden'); } }); // Inputs change -> regenerate ['basePathInput', 'settingOrder', 'settingAutoPlay', 'settingRemember', 'settingExtraIni', 'massechetTtsTemplate'].forEach(id => { const el = document.getElementById(id); if (el) { el.addEventListener('input', () => { State.basePath = document.getElementById('basePathInput').value.trim(); generateFullIvrPlan(); renderTree(); Simulator.goToRoot(); }); } }); // Test Connection Button document.getElementById('testConnectionBtn').addEventListener('click', async () => { const token = document.getElementById('apiTokenInput').value.trim(); const msgEl = document.getElementById('connectionMsg'); const badge = document.getElementById('apiStatusBadge'); if (!token) { msgEl.className = "text-xs p-2.5 rounded-lg border bg-rose-50 border-rose-200 text-rose-800"; msgEl.textContent = "אנא הזן טוקן API"; msgEl.classList.remove('hidden'); return; } State.apiToken = token; msgEl.className = "text-xs p-2.5 rounded-lg border bg-slate-100 border-slate-300 text-slate-700"; msgEl.textContent = "בודק חיבור מול ימות המשיח..."; msgEl.classList.remove('hidden'); try { const res = await YemotAPI.getSession(); if (res.responseStatus === 'OK') { msgEl.className = "text-xs p-2.5 rounded-lg border bg-emerald-50 border-emerald-200 text-emerald-800"; msgEl.innerHTML = `<strong>חיבור תקין!</strong> מערכת: ${res.username || ''} | שם: ${res.name || 'ללא'} | יחידות: ${res.units || 0}`; badge.className = "inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium bg-emerald-950 text-emerald-300 border border-emerald-700"; badge.innerHTML = `<span class="w-2 h-2 rounded-full bg-emerald-400"></span> מחובר למערכת`; log('SUCCESS', 'התחברות הצליחה מול שרת ימות המשיח', res); } else { msgEl.className = "text-xs p-2.5 rounded-lg border bg-rose-50 border-rose-200 text-rose-800"; msgEl.textContent = `שגיאה מהשרת: ${res.message || res.responseStatus}`; log('ERROR', 'שגיאת התחברות', res); } } catch (err) { msgEl.className = "text-xs p-2.5 rounded-lg border bg-rose-50 border-rose-200 text-rose-800"; msgEl.innerHTML = `שגיאת רשת / CORS: ${err.message}.<br><span class="text-[11px]">אם אתה משתמש ישירות בדפדפן, מומלץ לבחור באפשרות 'CORS Proxy' או להריץ מתוך שרת מקומי.</span>`; log('ERROR', 'שגיאת רשת בבדיקת חיבור', err.message); } }); // Clear logs document.getElementById('clearLogsBtn').addEventListener('click', () => { document.getElementById('logsConsole').innerHTML = ''; }); // Start Build document.getElementById('startBuildBtn').addEventListener('click', startBuildingExtensions); // Download backup JSON document.getElementById('downloadZipBtn').addEventListener('click', () => { const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(State.generatedPlan, null, 2)); const dlAnchor = document.createElement('a'); dlAnchor.setAttribute("href", dataStr); dlAnchor.setAttribute("download", `yemot_shas_plan_${Date.now()}.json`); document.body.appendChild(dlAnchor); dlAnchor.click(); dlAnchor.remove(); log('INFO', 'קובץ תוכנית השלוחות הורד למחשב המשתמש'); }); // View Session Details Modal / Toast document.getElementById('viewSessionBtn').addEventListener('click', async () => { const token = document.getElementById('apiTokenInput').value.trim(); if (!token) return alert('נא להזין טוקן תחילה'); try { const res = await YemotAPI.getSession(); alert(`פרטי מערכת:\nמספר: ${res.username || 'לא זמין'}\nשם: ${res.name || 'לא זמין'}\nיתרת יחידות: ${res.units || 0}\nתוקף יחידות: ${res.unitsExpireDate || 'ללא תפוגה'}`); } catch (e) { alert(`שגיאה בקבלת פרטים: ${e.message}`); } }); // Initial generation & render generateFullIvrPlan(); renderTree(); Simulator.init(); log('INFO', 'מערכת בניית שלוחות ש"ס מוכנה לפעולה'); } // Startup window.addEventListener('DOMContentLoaded', initUI); </script> </body> </html>```
שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.
נמאס לכם לגלול בין אותם הפוסטים בכל ביקור? כשנרשמים לחשבון, תמיד תחזרו בדיוק למקום שבו הייתם קודם, ותוכלו לבחור לקבל התראות על תגובות חדשות (בין אם במייל, ובין אם בהתראת פוש). תוכלו גם לשמור סימניות ולפרגן ב-upvote לפוסטים כדי להביע הערכה לחברי קהילה אחרים.
בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗
הרשמה התחברות