Hulasa: Belgeleri Özetleyin, Mail Taslağına Dönüştürün

Hulasa; uzun belgeleri daha hızlı incelemek, önemli noktaları belirlemek ve içerikten amaca uygun özet veya mail taslağı oluşturmak için geliştirilmiş pratik bir metin işleme uygulamasıdır. Yapmanız gereken tek şey, aşağıdaki kodu kopyalayıp not defterine yapıştırmak uzantıyı html olacak şekilde kaydetmek. Örnek isim "Hulasa.html" sonra, çift tıklayarak çalıştırdığınızda bir web browser ile açılıp çalışır.

Uygulamaya DOCX, XLSX, PDF, TXT, MD ve CSV formatındaki belgeler yüklenebilir. Dilerseniz herhangi bir metni “Belge İçeriği” alanına doğrudan yazabilir veya Ctrl+V ile yapıştırabilirsiniz. Türkçe karakterler ve paragraf yapısı korunurken belgenin kelime, karakter ve yaklaşık sayfa bilgileri otomatik olarak hesaplanır. Taranmış PDF belgelerinde ihtiyaç duyulması hâlinde Türkçe OCR desteği devreye girer.

Hulasa, içeriği yalnızca kısaltmakla kalmaz; seçilen türe göre farklı bir yapı ve anlatım düzeni oluşturur:

  • Konu Özeti: Belgenin kapsamını, temel noktalarını ve sonucunu sunar.
  • Yönetici Özeti: Kritik bulguları, kurumsal etkileri, öncelikleri ve önerileri yönetim bakış açısıyla düzenler.
  • Toplantı Notları: Gündem, görüşülen konular, alınan kararlar, aksiyonlar ve sorumluları ayrıştırır.
  • Ders Notları: Temel kavramları, açıklamaları, örnekleri ve önemli sonuçları öğrenmeyi kolaylaştıracak biçimde düzenler.
  • Yönetici Maili: Ekiplere veya çalışanlara gönderilebilecek; karar, sorumluluk ve aksiyon odaklı, uygun “rica” diliyle bir mail taslağı hazırlar.
  • Yönetime Mail: Üst yönetime sunulacak; risk, etki, ihtiyaç, değerlendirme ve onay konularını öne çıkaran, uygun “arz” diliyle bir mail taslağı oluşturur.

Nasıl kullanılır?

Belgenizi seçin veya metninizi ilgili alana yapıştırın. Ardından içerik uzunluğunu ve hazırlamak istediğiniz özet ya da mail türünü belirleyerek “Hazırla” düğmesine basın. Oluşturulan metni sayfa üzerinde düzenleyebilir, son hâlini Word belgesi olarak indirebilir veya PDF biçiminde kaydedebilirsiniz.

Hulasa, yalnızca bilgilendirme içeren metinlerle karar, aksiyon veya onay gerektiren içerikleri birbirinden ayırır. Böylece gereksiz talep ifadeleri oluşturmaz; mail konusu, hitap biçimi, bölüm düzeni ve kapanış ifadesini içeriğin amacına göre belirler.

Uygulama, belge içeriğini tarayıcı üzerinde yerel olarak işlemek ve dış servislere aktarmamak üzere tasarlanmıştır. Bu yaklaşım, özellikle kurumsal ve hassas belgeler üzerinde çalışırken daha kontrollü bir kullanım sağlar.

Kısacası Hulasa; uzun metinleri daha anlaşılır, yönetilebilir ve kullanılabilir çıktılara dönüştürerek belge inceleme ve kurumsal yazışma süreçlerini kolaylaştırır.


Uygulama Görselleri:








+ Projenin Android telefonlar için mobil uygulamaya dönüştürülmüş hali aşağıdaki paylaşımdadır. "Hulasa.apk" indir.

HTML Kod:

<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Hulasa</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob: data:; script-src 'self' 'unsafe-inline' blob:; worker-src 'self' blob:; connect-src 'self' blob: data:; img-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; frame-src 'none'; form-action 'none'; base-uri 'none'">
<script src="./vendor/mammoth.browser.min.js"></script>
<script src="./vendor/xlsx.full.min.js"></script>
<script src="./vendor/pdf.min.js"></script>
<script src="./vendor/pdf.worker.min.js"></script>
<script src="./vendor/tesseract.min.js"></script>
<style>
:root{--bg:#f8fafc;--panel:#fff;--line:#dbe3ea;--text:#0f172a;--muted:#64748b;--blue:#0284c7;--blue2:#e0f2fe;--green:#16a34a;--green2:#dcfce7;--red:#dc2626;--shadow:0 4px 18px rgba(15,23,42,.06);--r:6px}
*{box-sizing:border-box}html,body{min-height:100%}body{margin:0;background:var(--bg);color:var(--text);font-family:"Segoe UI",Arial,sans-serif}button,input,select{font:inherit}button{cursor:pointer}.app{min-height:100vh;display:flex;flex-direction:column}.topbar{position:sticky;top:0;z-index:20;background:#fff;border-bottom:1px solid var(--line);box-shadow:var(--shadow)}.head{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 20px 11px}.brand-title{font-size:23px;font-weight:800;line-height:1.1}.brand-sub{margin-top:4px;color:var(--muted);font-size:12.5px}.file-pill{display:flex;align-items:center;gap:7px;max-width:min(660px,55vw);padding:6px 9px;border:1px solid var(--line);border-radius:var(--r);background:#f8fafc;font-size:12px}.file-pill b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toolbar{display:flex;flex-wrap:wrap;align-items:end;gap:9px;padding:10px 20px 13px;border-top:1px solid #eef2f7}.field{display:flex;flex-direction:column;gap:4px}.field label{font-size:11.5px;color:#475569;font-weight:700}.input,select,.btn,.file-btn{height:38px;border:1px solid var(--line);border-radius:var(--r);background:#fff;color:var(--text)}.input,select{padding:0 10px;outline:none}.input:focus,select:focus{border-color:var(--blue);box-shadow:0 0 0 2px rgba(2,132,199,.12)}#summaryLimit{width:175px}#summaryType{min-width:190px}.btn,.file-btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:0 13px;font-weight:700;white-space:nowrap}.btn:hover,.file-btn:hover{background:#f8fafc}.btn.primary{background:var(--green2);border-color:#86efac;color:#166534}.btn.primary:hover{background:#bbf7d0}.btn.export{background:#f1f5f9;border-color:#cbd5e1;color:#334155}.btn:disabled{opacity:.48;cursor:not-allowed}.file-input{display:none}.push{margin-left:auto}.author{width:100%;text-align:right;color:#cbd5e1;font-size:10px;margin-top:-3px}.progress-wrap{display:none;padding:0 20px 12px}.progress-line{display:flex;align-items:center;gap:10px}.progress{height:10px;flex:1;border-radius:999px;overflow:hidden;background:#e5e7eb}.bar{height:100%;width:0;background:linear-gradient(90deg,#0284c7,#38bdf8);transition:width .15s}.progress-label{width:170px;text-align:right;font-size:12px;font-weight:700;color:#334155;white-space:nowrap}.main{width:100%;max-width:1500px;margin:0 auto;padding:18px 20px 30px}.panel{margin-bottom:16px;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden}.panel-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;border-bottom:1px solid var(--line)}.panel-title{font-size:15px;font-weight:800}.panel-sub{margin-top:2px;font-size:12px;color:var(--muted)}.panel-body{padding:14px}.toggle{border:1px solid var(--line);background:#fff;border-radius:var(--r);padding:6px 10px;font-weight:700;color:#334155}.source{display:block;min-height:130px;max-height:320px;overflow:auto;white-space:pre-wrap;padding:12px;border:1px solid var(--line);border-radius:var(--r);background:#f8fafc;font:12.5px/1.58 "Segoe UI",Arial,sans-serif;outline:none}.source:focus{border-color:var(--blue);box-shadow:0 0 0 2px rgba(2,132,199,.12);background:#fff}.source:empty:before{content:attr(data-placeholder);color:#94a3b8;pointer-events:none}.notice{margin-top:8px;padding:13px;border:1px dashed #cbd5e1;border-radius:var(--r);background:#f8fafc;color:var(--muted);font-size:13px}.stats{display:none;grid-template-columns:repeat(4,minmax(130px,1fr));gap:8px}.stat{border:1px solid var(--line);border-radius:var(--r);padding:10px;background:#fff}.stat b{display:block;font-size:18px}.stat span{font-size:11px;color:var(--muted)}.paper-wrap{display:none;gap:18px;flex-direction:column;align-items:center;background:#e9eef3;padding:18px}.paper{position:relative;width:min(210mm,100%);min-height:297mm;background:#fff;padding:20mm 18mm 22mm;box-shadow:0 2px 12px rgba(15,23,42,.12);font:11pt/1.58 Calibri,"Segoe UI",Arial,sans-serif}.paper h1{font-size:18pt;margin:0 0 2mm}.paper .subtitle{color:#64748b;font-size:9.5pt;border-bottom:1px solid #cbd5e1;padding-bottom:4mm;margin-bottom:7mm}.paper .summary-text{white-space:normal;text-align:justify}.paper .summary-text p{margin:0 0 4.5mm}.paper .summary-text h2{font-size:13pt;line-height:1.25;margin:7mm 0 3mm;break-after:avoid;page-break-after:avoid}.paper .summary-text h2:first-child{margin-top:0}.paper .summary-text h2.continued{font-size:11.5pt;color:#475569;margin-top:0}.paper .summary-text ul{margin:1.5mm 0 5mm;padding-left:7mm}.paper .summary-text li{margin:0 0 2.2mm;padding-left:1mm}.page-no{position:absolute;left:18mm;right:18mm;bottom:9mm;text-align:center;color:#64748b;font-size:9pt;border-top:1px solid #e2e8f0;padding-top:3mm}.toast{display:none;position:fixed;right:18px;bottom:18px;z-index:100;padding:11px 14px;border-radius:var(--r);background:#0f172a;color:#fff;box-shadow:0 8px 24px rgba(0,0,0,.22);max-width:430px;font-size:13px}.screen-only{}
.stats{grid-template-columns:repeat(3,minmax(130px,1fr))}
@media(max-width:760px){.head{align-items:flex-start;flex-direction:column}.file-pill{max-width:100%;width:100%}.toolbar>*{flex:1 1 145px}.toolbar .field{min-width:145px}.push{margin-left:0}.stats{grid-template-columns:repeat(2,1fr)}.main{padding:10px}.paper-wrap{padding:6px}.paper{min-height:0;padding:14mm 10mm 18mm}.progress-label{width:112px}.author{flex-basis:100%}}
@media print{@page{size:A4 portrait;margin:0}body{background:#fff}.no-print,.topbar,.source-panel,.stats-panel{display:none!important}.main{max-width:none;padding:0}.panel{border:0;box-shadow:none;margin:0}.panel-head{display:none}.panel-body{padding:0}.paper-wrap{display:block!important;background:#fff;padding:0}.paper{width:210mm;min-height:297mm;box-shadow:none;margin:0;break-after:page;page-break-after:always}.paper:last-child{break-after:auto;page-break-after:auto}}
</style>
</head>
<body>
<div class="app">
  <header class="topbar no-print">
    <div class="head">
      <div><div class="brand-title">Hulasa</div><div class="brand-sub">(Özet, Kısa sonuç, Mail düzenle)</div></div>
      <div class="file-pill"><span>📄</span><span>Seçilen Belge:</span><b id="fileName">Henüz belge seçilmedi</b></div>
    </div>
    <div class="toolbar">
      <button class="btn export" id="refreshBtn">↻ Yenile</button>
      <div class="field"><label>Belge</label><label class="file-btn">📂 Dosya Seç<input class="file-input" id="fileInput" type="file" accept=".docx,.xlsx,.pdf,.txt,.md,.csv"></label></div>
      <div class="field"><label>Uzunluk sınırı</label><input class="input" id="summaryLimit" list="limitList" placeholder="Değer Seçin" autocomplete="off"><datalist id="limitList"><option value="1 Paragraf"><option value="2 Paragraf"><option value="1 Sayfa"><option value="2 Sayfa"><option value="3 Sayfa"><option value="5 Sayfa"></datalist></div>
      <div class="field"><label>Özet / Mail Türü</label><select id="summaryType"><option value="Konu Özeti">Konu Özeti</option><option value="Yönetici Özeti">Yönetici Özeti</option><option value="Toplantı Notları">Toplantı Notları</option><option value="Ders Notları">Ders Notları</option><option value="Yönetici Maili">Yönetici Maili</option><option value="Yönetime Mail">Yönetime Mail</option></select></div>
      <button class="btn primary" id="prepareBtn">✦ Hazırla</button>
      <button class="btn export push" id="printBtn" disabled>🖨️ PDF / Kaydet</button>
      <button class="btn export" id="wordBtn" disabled>📘 Word İndir</button>
      <div class="author">Muhammed Yusuf OLGUN</div>
    </div>
    <div class="progress-wrap" id="progressWrap"><div class="progress-line"><div class="progress"><div class="bar" id="progressBar"></div></div><div class="progress-label" id="progressLabel">0% • Bekleniyor</div></div></div>
  </header>

  <main class="main">
    <section class="panel source-panel">
      <div class="panel-head"><div><div class="panel-title">Belge İçeriği</div><div class="panel-sub" id="sourceMeta">Dosya seçin veya aşağıdaki alana tıklayıp Ctrl+V ile metin yapıştırın.</div></div><button class="toggle no-print" id="sourceToggle">Gizle ↑</button></div>
      <div class="panel-body"><div class="source" id="sourceText" contenteditable="true" role="textbox" aria-multiline="true" spellcheck="true" tabindex="0" data-placeholder="Metni buraya yazın veya Ctrl+V ile yapıştırın…"></div><div class="notice" id="sourceNotice">Metni doğrudan yazabilir ya da Ctrl+V ile yapıştırabilirsiniz. Türkçe karakter uyumlu dosyalar: DOCX, XLSX, PDF ve UTF-8/UTF-16/Windows-1254 kodlamalı TXT, MD, CSV.</div></div>
    </section>

    <section class="panel stats-panel"><div class="panel-body"><div class="stats" id="stats"><div class="stat"><b id="wordCount">0</b><span>Kelime</span></div><div class="stat"><b id="charCount">0</b><span>Karakter</span></div><div class="stat"><b id="pageCount">0</b><span>Yaklaşık A4 sayfası</span></div></div></div></section>

    <section class="panel"><div class="panel-head"><div><div class="panel-title">Hazırlanan Özet / Mail</div><div class="panel-sub" id="summaryMeta">Özet veya mail hazırlamak için belge ya da metin ekleyin.</div></div></div><div class="panel-body"><div class="notice" id="summaryNotice">İçerik eklendikten sonra uzunluğu ve türü belirleyip “Hazırla” düğmesine basın.</div><div class="paper-wrap" id="paperWrap"></div></div></section>
  </main>
</div>
<div class="toast" id="toast"></div>

<script>
"use strict";
/* ÇEVRİMDIŞI GÜVENLİK KİLİDİ: yalnızca aynı klasördeki yerel bileşenlere izin verir. */
const isLocalResource=value=>{try{const raw=typeof value==="string"?value:(value&&value.url)||String(value);const u=new URL(raw,location.href);return u.protocol==="blob:"||u.protocol==="data:"||u.protocol==="file:"||u.origin===location.origin}catch(_){return false}};
if(window.fetch){const nativeFetch=window.fetch.bind(window);window.fetch=(input,init)=>isLocalResource(input)?nativeFetch(input,init):Promise.reject(new Error("Dış ağ bağlantısı güvenlik politikası tarafından engellendi."))}
if(window.XMLHttpRequest){const nativeOpen=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(method,url,...rest){if(!isLocalResource(url))throw new Error("Dış ağ bağlantısı güvenlik politikası tarafından engellendi.");return nativeOpen.call(this,method,url,...rest)}}
window.WebSocket=class{constructor(){throw new Error("WebSocket bağlantıları devre dışıdır.")}};
window.EventSource=class{constructor(){throw new Error("EventSource bağlantıları devre dışıdır.")}};
try{navigator.sendBeacon=()=>false}catch(_){}
const state={fileName:"",source:"",summary:"",pages:[],ocrUsed:false};
const $=s=>document.querySelector(s);
const el={fileInput:$("#fileInput"),fileName:$("#fileName"),sourceText:$("#sourceText"),sourceMeta:$("#sourceMeta"),sourceNotice:$("#sourceNotice"),sourceToggle:$("#sourceToggle"),stats:$("#stats"),wordCount:$("#wordCount"),charCount:$("#charCount"),pageCount:$("#pageCount"),summaryLimit:$("#summaryLimit"),summaryType:$("#summaryType"),prepareBtn:$("#prepareBtn"),printBtn:$("#printBtn"),wordBtn:$("#wordBtn"),summaryMeta:$("#summaryMeta"),summaryNotice:$("#summaryNotice"),paperWrap:$("#paperWrap"),progressWrap:$("#progressWrap"),progressBar:$("#progressBar"),progressLabel:$("#progressLabel"),toast:$("#toast")};
function toast(s,ms=4000){el.toast.textContent=s;el.toast.style.display="block";clearTimeout(toast.t);toast.t=setTimeout(()=>el.toast.style.display="none",ms)}
function progress(n,label){n=Math.max(0,Math.min(100,Math.round(n)));el.progressWrap.style.display="block";el.progressBar.style.width=n+"%";el.progressLabel.textContent=n+"% • "+label;if(n===100)setTimeout(()=>el.progressWrap.style.display="none",1300)}
function esc(s=""){return String(s).replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]))}
const windows1252Bytes=new Map([[0x20AC,0x80],[0x201A,0x82],[0x0192,0x83],[0x201E,0x84],[0x2026,0x85],[0x2020,0x86],[0x2021,0x87],[0x02C6,0x88],[0x2030,0x89],[0x0160,0x8A],[0x2039,0x8B],[0x0152,0x8C],[0x017D,0x8E],[0x2018,0x91],[0x2019,0x92],[0x201C,0x93],[0x201D,0x94],[0x2022,0x95],[0x2013,0x96],[0x2014,0x97],[0x02DC,0x98],[0x2122,0x99],[0x0161,0x9A],[0x203A,0x9B],[0x0153,0x9C],[0x017E,0x9E],[0x0178,0x9F]]);
function decodeWindows1252AsUtf8(value){const bytes=[];for(const ch of String(value)){const cp=ch.codePointAt(0);if(cp<=255)bytes.push(cp);else if(windows1252Bytes.has(cp))bytes.push(windows1252Bytes.get(cp));else return null}try{return new TextDecoder("utf-8",{fatal:true}).decode(new Uint8Array(bytes))}catch(_){return null}}
function mojibakePenalty(value){const s=String(value||"");return (s.match(/[ÃÂÄÅâ]/g)||[]).length*20+(s.match(/[\u0080-\u009f]/g)||[]).length*25}
function directTurkishRepair(value){let s=String(value||"");const fixes=[["Ç","Ç"],["ç","ç"],["Äž","Ğ"],["ÄŸ","ğ"],["İ","İ"],["ı","ı"],["Ö","Ö"],["ö","ö"],["Åž","Ş"],["ÅŸ","ş"],["Ü","Ü"],["ü","ü"],["’","’"],["“","“"],["”","”"],["–","–"],["—","—"],[" "," "],["&Ccedil;","Ç"],["&ccedil;","ç"],["&Gbreve;","Ğ"],["&gbreve;","ğ"],["&Idot;","İ"],["&inodot;","ı"],["&Ouml;","Ö"],["&ouml;","ö"],["&Scedil;","Ş"],["&scedil;","ş"],["&Uuml;","Ü"],["&uuml;","ü"]];for(const [bad,good] of fixes)s=s.split(bad).join(good);return s}
function repairTurkishEncoding(value){let current=String(value||"");for(let pass=0;pass<4;pass++){const candidates=[current,directTurkishRepair(current)];const decoded=decodeWindows1252AsUtf8(current);if(decoded!==null)candidates.push(decoded,directTurkishRepair(decoded));const directDecoded=decodeWindows1252AsUtf8(candidates[1]);if(directDecoded!==null)candidates.push(directDecoded);candidates.sort((a,b)=>mojibakePenalty(a)-mojibakePenalty(b));const best=candidates[0];if(best===current||mojibakePenalty(best)>=mojibakePenalty(current))break;current=best}return directTurkishRepair(current).normalize("NFC")}
function normalize(s){return String(s||"").normalize("NFC").replace(/\u0000/g," ").replace(/\r\n?/g,"\n").replace(/[ \t]+/g," ").replace(/ *\n */g,"\n").replace(/\n{3,}/g,"\n\n").replace(/ +([,.;:!?])/g,"$1").trim()}
function normalizeImported(s){return normalize(repairTurkishEncoding(s))}
function decodeTextDocument(arrayBuffer){
 const bytes=new Uint8Array(arrayBuffer);
 if(bytes.length>=3&&bytes[0]===0xEF&&bytes[1]===0xBB&&bytes[2]===0xBF)return new TextDecoder("utf-8").decode(bytes.subarray(3));
 if(bytes.length>=2&&bytes[0]===0xFF&&bytes[1]===0xFE)return new TextDecoder("utf-16le").decode(bytes.subarray(2));
 if(bytes.length>=2&&bytes[0]===0xFE&&bytes[1]===0xFF){const body=bytes.subarray(2),swapped=new Uint8Array(body.length);for(let i=0;i+1<body.length;i+=2){swapped[i]=body[i+1];swapped[i+1]=body[i]}return new TextDecoder("utf-16le").decode(swapped)}
 try{return new TextDecoder("utf-8",{fatal:true}).decode(bytes)}catch(_){return new TextDecoder("windows-1254").decode(bytes)}
}
function textQuality(s){s=String(s||"");const wc=words(s).length,letters=(s.match(/\p{L}/gu)||[]).length,bad=(s.match(/[□■ ]|Ã.|Ä.|Å.|Â.|(?:[^\s\p{L}\p{N}.,;:!?()'’%\-–—/]){3,}/gu)||[]).length;return wc*3+letters/25-bad*30}
function needsTurkishOcr(s){return words(s).length<12||textQuality(s)<20||/[□■ ]|Ã.|Ä.|Å.|Â./u.test(s)}
function words(s){return (s.match(/[\p{L}\p{N}]+(?:['’.-][\p{L}\p{N}]+)*/gu)||[])}
function sleep(ms){return new Promise(r=>setTimeout(r,ms))}
function requireLocal(test,name){if(!test())throw new Error(name+" yerel bileşeni bulunamadı. Paketin vendor klasörü eksiksiz olmalıdır.")}
function resetSummary(){state.summary="";state.pages=[];el.paperWrap.innerHTML="";el.paperWrap.style.display="none";el.summaryNotice.style.display="block";el.printBtn.disabled=true;el.wordBtn.disabled=true;el.summaryMeta.textContent="Özet veya mail henüz hazırlanmadı."}
function updateSourceStats(text,label){const wc=words(text).length,pages=Math.max(1,Math.ceil(wc/450));el.wordCount.textContent=wc.toLocaleString("tr-TR");el.charCount.textContent=text.length.toLocaleString("tr-TR");el.pageCount.textContent=pages.toLocaleString("tr-TR");el.stats.style.display="grid";el.sourceMeta.textContent=`${label} • ${wc.toLocaleString("tr-TR")} kelime • Yaklaşık ${pages.toLocaleString("tr-TR")} A4 sayfası`;return{wc,pages}}
function syncSourceEditor(label="Metin güncellendi"){clearTimeout(syncSourceEditor.t);syncSourceEditor.t=setTimeout(()=>{resetSummary();const text=normalizeImported(el.sourceText.innerText||el.sourceText.textContent||"");state.source=text;if(!text){el.stats.style.display="none";el.sourceNotice.style.display="block";el.sourceMeta.textContent="Dosya seçin veya aşağıdaki alana tıklayıp Ctrl+V ile metin yapıştırın.";if(state.fileName==="Yapıştırılan Metin"){state.fileName="";el.fileName.textContent="Henüz belge seçilmedi"}return}if(!state.fileName){state.fileName="Yapıştırılan Metin";el.fileName.textContent=state.fileName;el.fileName.title=state.fileName}el.sourceNotice.style.display="none";updateSourceStats(text,label)},120)}
function insertPlainText(text){const clean=String(text||"").replace(/\r\n?/g,"\n"),sel=window.getSelection&&window.getSelection();if(sel&&sel.rangeCount&&el.sourceText.contains(sel.anchorNode)){const range=sel.getRangeAt(0);range.deleteContents();const node=document.createTextNode(clean);range.insertNode(node);range.setStartAfter(node);range.collapse(true);sel.removeAllRanges();sel.addRange(range)}else el.sourceText.appendChild(document.createTextNode(clean))}
$("#refreshBtn").addEventListener("click",()=>location.reload());
el.fileInput.addEventListener("click",()=>el.fileInput.value="");
el.sourceToggle.addEventListener("click",()=>{const show=getComputedStyle(el.sourceText).display==="none";el.sourceText.style.display=show?"block":"none";el.sourceToggle.textContent=show?"Gizle ↑":"Göster ↓";if(show)el.sourceText.focus()});
el.sourceText.addEventListener("paste",e=>{const text=e.clipboardData&&e.clipboardData.getData("text/plain");if(typeof text!=="string")return;e.preventDefault();insertPlainText(text);syncSourceEditor("Ctrl+V ile yapıştırıldı");toast("Metin belge içeriğine eklendi.")});
el.sourceText.addEventListener("input",()=>syncSourceEditor("Metin düzenlendi"));

el.fileInput.addEventListener("change",async e=>{
 const file=e.target.files&&e.target.files[0];if(!file)return;resetSummary();state.fileName=file.name;state.source="";state.ocrUsed=false;el.fileName.textContent=file.name;el.fileName.title=file.name;el.sourceText.style.display="none";el.sourceToggle.textContent="Göster ↓";el.sourceNotice.style.display="none";el.sourceMeta.textContent="Belge içeri alınıyor…";progress(2,"Dosya okunuyor");
 try{const ext=(file.name.split(".").pop()||"").toLowerCase();let text="";
  if(["txt","md","csv"].includes(ext)){text=decodeTextDocument(await file.arrayBuffer());progress(82,"Metin kodlaması doğrulanıyor")}
  else if(ext==="docx"){text=await readDocx(file)}
  else if(ext==="xlsx"){text=await readExcel(file)}
  else if(ext==="pdf"){text=await readPdf(file)}
  else throw new Error("Desteklenmeyen dosya türü. DOCX, XLSX, PDF, TXT, MD veya CSV seçin.");
  text=normalizeImported(text);if(words(text).length<5)throw new Error("Belgeden yeterli metin çıkarılamadı.");state.source=text;el.sourceText.textContent=text;const wc=words(text).length;const pages=Math.max(1,Math.ceil(wc/450));el.wordCount.textContent=wc.toLocaleString("tr-TR");el.charCount.textContent=text.length.toLocaleString("tr-TR");el.pageCount.textContent=pages.toLocaleString("tr-TR");el.stats.style.display="grid";el.sourceMeta.textContent=`Alındı • ${wc.toLocaleString("tr-TR")} kelime • Yaklaşık ${pages.toLocaleString("tr-TR")} A4 sayfası${state.ocrUsed?" • Türkçe OCR uygulandı":""}`;progress(100,"İçeri alındı");toast("Belge Türkçe karakterleri korunarak içeri alındı.");
 }catch(err){console.error(err);state.source="";el.sourceText.textContent="";el.sourceNotice.style.display="block";const reason=err&&err.message?err.message:String(err);el.sourceNotice.textContent="Belge okunamadı: "+reason;el.sourceMeta.textContent="İçeri alma başarısız: "+reason;progress(100,"Hata");toast("Belge okunamadı: "+reason,8000)}
});

async function readDocx(file){progress(10,"Word belgesi yerel olarak okunuyor");try{requireLocal(()=>!!window.mammoth,"Word okuyucu");const r=await mammoth.extractRawText({arrayBuffer:await file.arrayBuffer()});progress(86,"Word metni düzenleniyor");return r.value}catch(err){console.warn(err);return readDocxFallback(await file.arrayBuffer())}}
async function readDocxFallback(ab){const z=await unzip(ab),d=z.get("word/document.xml");if(!d)throw new Error("DOCX metni bulunamadı.");return xmlEntities(new TextDecoder().decode(d).replace(/<w:tab[^>]*\/>/g,"\t").replace(/<w:br[^>]*\/>/g,"\n").replace(/<\/w:p>/g,"\n").replace(/<[^>]+>/g,""))}
async function readExcel(file){progress(10,"Excel belgesi yerel olarak okunuyor");requireLocal(()=>!!window.XLSX,"Excel okuyucu");const wb=XLSX.read(await file.arrayBuffer(),{type:"array",cellDates:true});const out=[];wb.SheetNames.forEach(n=>{out.push("[Sayfa: "+n+"]");out.push(XLSX.utils.sheet_to_csv(wb.Sheets[n],{FS:" | ",blankrows:false}))});progress(86,"Excel hücreleri metne dönüştürüldü");return out.join("\n")}
async function loadPdfTask(engine,options){const task=engine.getDocument(options);task.onPassword=(setPassword,reason)=>{const password=window.prompt(reason===2?"PDF parolası yanlış. Lütfen tekrar girin:":"Bu PDF parola ile korunuyor. Parolayı girin:","");if(password===null){try{task.destroy()}catch(_){}return}setPassword(password)};return task.promise}
async function openPdfLocally(arrayBuffer){const vendorBase=new URL("./vendor/",location.href);pdfjsLib.GlobalWorkerOptions.workerSrc=new URL("pdf.worker.min.js",vendorBase).href;const common={cMapUrl:new URL("cmaps/",vendorBase).href,cMapPacked:true,standardFontDataUrl:new URL("standard_fonts/",vendorBase).href,useSystemFonts:true,useWorkerFetch:false,stopAtErrors:false};let firstError;try{return await loadPdfTask(pdfjsLib,{...common,data:new Uint8Array(arrayBuffer.slice(0))})}catch(err){firstError=err;console.warn("Birincil PDF okuyucu ilk denemesi başarısız:",err)}try{return await loadPdfTask(pdfjsLib,{...common,data:new Uint8Array(arrayBuffer.slice(0)),disableFontFace:true,isEvalSupported:false})}catch(err){console.warn("Birincil PDF okuyucu güvenli denemesi başarısız:",err)}try{const pdf5Base=new URL("pdf5/",vendorBase),pdf5=await import(new URL("pdf.min.mjs",pdf5Base).href);pdf5.GlobalWorkerOptions.workerSrc=new URL("pdf.worker.min.mjs",pdf5Base).href;return await loadPdfTask(pdf5,{data:new Uint8Array(arrayBuffer.slice(0)),cMapUrl:new URL("cmaps/",pdf5Base).href,cMapPacked:true,standardFontDataUrl:new URL("standard_fonts/",pdf5Base).href,wasmUrl:new URL("wasm/",pdf5Base).href,iccUrl:new URL("iccs/",pdf5Base).href,useWorkerFetch:false,useSystemFonts:true,stopAtErrors:false,isEvalSupported:false})}catch(lastError){console.error("İkinci PDF motoru da belgeyi açamadı:",lastError);throw new Error(`PDF açılamadı. Birincil okuyucu: ${(firstError&&firstError.message)||"bilinmeyen hata"}. İkinci okuyucu: ${(lastError&&lastError.message)||"bilinmeyen hata"}.`)}}
async function readPdf(file){progress(8,"PDF yerel olarak hazırlanıyor");requireLocal(()=>!!window.pdfjsLib,"PDF okuyucu");let pdf;try{pdf=await openPdfLocally(await file.arrayBuffer())}catch(err){throw new Error("PDF okuyucu belgeyi açamadı: "+(err&&err.message?err.message:"Bilinmeyen PDF hatası"))}const texts=[],ocrPages=[];for(let i=1;i<=pdf.numPages;i++){let t="";try{const p=await pdf.getPage(i),content=await p.getTextContent({includeMarkedContent:false,disableNormalization:false});t=normalize(content.items.map(x=>(x.str||"")+(x.hasEOL?"\n":" ")).join(""))}catch(pageError){console.warn(`PDF ${i}. sayfa metin katmanı okunamadı:`,pageError)}texts.push(t);if(needsTurkishOcr(t))ocrPages.push(i);progress(10+Math.round(i/pdf.numPages*42),`PDF metni okunuyor (${i}/${pdf.numPages})`);await sleep(0)}
 if(ocrPages.length){requireLocal(()=>!!window.Tesseract,"OCR");progress(54,`Türkçe OCR hazırlanıyor (${ocrPages.length} sayfa)`);let worker=null,replaced=0;try{worker=await Tesseract.createWorker("tur+eng",1,{workerPath:"./vendor/tesseract-worker.min.js",corePath:"./vendor",langPath:"./tessdata",logger:m=>{if(m.status==="recognizing text")progress(55+Math.round((m.progress||0)*35),"Türkçe OCR ile metne çevriliyor")}});if(worker.setParameters)await worker.setParameters({preserve_interword_spaces:"1",user_defined_dpi:"220"});for(let j=0;j<ocrPages.length;j++){const no=ocrPages[j],p=await pdf.getPage(no),vp=p.getViewport({scale:2.2}),canvas=document.createElement("canvas");canvas.width=Math.ceil(vp.width);canvas.height=Math.ceil(vp.height);const ctx=canvas.getContext("2d",{alpha:false});ctx.fillStyle="#fff";ctx.fillRect(0,0,canvas.width,canvas.height);await p.render({canvasContext:ctx,viewport:vp,background:"white"}).promise;const r=await worker.recognize(canvas),candidate=normalize((r.data&&r.data.text)||"");if(textQuality(candidate)>textQuality(texts[no-1])||words(texts[no-1]).length<5){texts[no-1]=candidate;replaced++}progress(55+Math.round((j+1)/ocrPages.length*38),`Türkçe OCR tamamlanıyor (${j+1}/${ocrPages.length})`);canvas.width=canvas.height=1}state.ocrUsed=replaced>0}finally{if(worker)await worker.terminate()}}
 progress(94,"PDF sayfaları birleştiriliyor");return texts.map((t,i)=>`[Sayfa ${i+1}]\n${t}`).join("\n\n")}
async function unzip(ab){const b=new Uint8Array(ab),dv=new DataView(ab);let e=-1;for(let i=b.length-22;i>=Math.max(0,b.length-65557);i--)if(dv.getUint32(i,true)===0x06054b50){e=i;break}if(e<0)throw new Error("ZIP yapısı bulunamadı.");const count=dv.getUint16(e+10,true),off=dv.getUint32(e+16,true),m=new Map();let p=off;for(let n=0;n<count;n++){if(dv.getUint32(p,true)!==0x02014b50)break;const method=dv.getUint16(p+10,true),size=dv.getUint32(p+20,true),nl=dv.getUint16(p+28,true),xl=dv.getUint16(p+30,true),cl=dv.getUint16(p+32,true),lo=dv.getUint32(p+42,true),name=new TextDecoder().decode(b.slice(p+46,p+46+nl)),ln=dv.getUint16(lo+26,true),le=dv.getUint16(lo+28,true),start=lo+30+ln+le,raw=b.slice(start,start+size);let data;if(method===0)data=raw;else if(method===8){const ds=new DecompressionStream("deflate-raw");data=new Uint8Array(await new Response(new Blob([raw]).stream().pipeThrough(ds)).arrayBuffer())}if(data)m.set(name,data);p+=46+nl+xl+cl}return m}
function xmlEntities(s){return s.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&apos;/g,"'")}

const stop=new Set("acaba ama ancak artık aslında az bazı belki ben beni benim beş bile bir birçok biri birkaç biz bize bizim bu bunu bunun çünkü da daha de değil diğer diye dolayı dört en fakat gibi göre hala hangi hatta hem hep hepsi her hiç için iki ile ise işte kadar karşı kendi ki kim madem mi mı mu mü nasıl ne neden nedir neredeyse niçin o olan olarak oldu onlar onu onun önce rağmen sen seni sizin sonra şöyle şu şunu tarafından tüm üç üzere var ve veya ya yani yeni yine yok çok çünkü içindeki ile ilgili ilgili olarak olduğu olması gereken gerekmektedir yapılmıştır yapılması tarafından ayrıca ancak önemli kapsamında konusu sonucunda şekilde yer alan ilgili yönelik ait dair".split(/\s+/));
function sentenceList(text){return text.replace(/\[Sayfa \d+\]/g," ").split(/(?<=[.!?])\s+|\n+/u).map(s=>s.trim()).filter(s=>words(s).length>=3&&s.length<900)}
function matchCount(s,rx){return (String(s).match(rx)||[]).length}
function typeScore(s,type,index,total){let bonus=0;const pos=index/Math.max(1,total-1);if(type==="Yönetici Özeti"){bonus+=matchCount(s,/hedef|risk|etki|öncelik|karar|öner|stratej|maliyet|bütçe|performans|kritik|sonuç|kurum/gi)*1.9;if(/\d|%|milyon|milyar/i.test(s))bonus+=1.2;if(pos>.7)bonus+=.4}else if(type==="Yönetici Maili"){bonus+=matchCount(s,/karar|aksiyon|eylem|sorumlu|termin|tarih|plan|uygula|tamamla|takip|koordinasyon|gerek|öncelik/gi)*2.2;if(/\d{1,2}[./-]\d{1,2}|saat|tarih|kadar/i.test(s))bonus+=1.1}else if(type==="Yönetime Mail"){bonus+=matchCount(s,/risk|etki|kritik|sonuç|karar|öner|maliyet|bütçe|stratej|hedef|ihtiyaç|onay|talep|kurum|hizmet/gi)*2.15;if(/\d|%|milyon|milyar/i.test(s))bonus+=1.2}else if(type==="Toplantı Notları"){bonus+=matchCount(s,/gündem|toplantı|katılımcı|görüş|değerlendir|karar|kararlaştır|aksiyon|eylem|sorumlu|termin|tarih|plan|takip/gi)*2.15;if(/\d{1,2}[./-]\d{1,2}|saat|tarih/i.test(s))bonus+=1.1}else if(type==="Ders Notları"){bonus+=matchCount(s,/tanım|kavram|ifade eder|denir|örnek|özellik|neden|nasıl|yöntem|aşama|süreç|sonuç|önem|temel|uygulama/gi)*1.75;if(/örneğin|başka bir deyişle|özetle/i.test(s))bonus+=1.1}else{bonus+=matchCount(s,/konu|amaç|kapsam|temel|ana|sonuç|özetle/gi)*.75;if(pos<.18)bonus+=.65}return bonus}
function scoreSentences(text,type){const list=sentenceList(text),freq=new Map();words(text.toLocaleLowerCase("tr-TR")).forEach(w=>{w=w.replace(/^[-.]+|[-.]+$/g,"");if(w.length>2&&!stop.has(w))freq.set(w,(freq.get(w)||0)+1)});const max=Math.max(1,...freq.values());return list.map((s,i)=>{const ws=words(s.toLocaleLowerCase("tr-TR"));let score=ws.reduce((a,w)=>a+(freq.get(w)||0)/max,0)/Math.sqrt(Math.max(1,ws.length));score+=typeScore(s,type,i,list.length);if(i<Math.max(2,list.length*.06))score+=.35;return{s,i,score,vec:new Set(ws.filter(w=>w.length>2&&!stop.has(w)))}})}
function similarity(a,b){let n=0;a.forEach(x=>{if(b.has(x))n++});return n/Math.max(1,Math.min(a.size,b.size))}
function limitSpec(v){const x=v.trim().toLocaleLowerCase("tr-TR");const map={"1 paragraf":{words:110,p:1},"2 paragraf":{words:220,p:2},"1 sayfa":{words:450,p:4},"2 sayfa":{words:900,p:8},"3 sayfa":{words:1350,p:12},"5 sayfa":{words:2250,p:20}};if(map[x])return map[x];const n=parseInt(x,10);if(Number.isFinite(n)&&n>0)return /paragraf/.test(x)?{words:n*110,p:n}:{words:n*450,p:n*4};throw new Error("Özet sınırı listesinden bir değer seçin veya örneğin ‘4 Sayfa’ / ‘3 Paragraf’ yazın.")}
function selectSummaryItems(text,type,spec){const scored=scoreSentences(text,type),rank=[...scored].sort((a,b)=>b.score-a.score),selected=new Map();let total=0;const dependent=/^(bu|bunun|buna|bunu|bunlar|böylece|ancak|fakat|dolayısıyla|bu nedenle|bu kapsamda|ayrıca|öte yandan|sonuç olarak)\b/i;const threshold=type==="Toplantı Notları"?.86:.79;for(const anchor of rank){if(total>=spec.words)break;if([...selected.values()].some(p=>similarity(p.vec,anchor.vec)>threshold))continue;const context=[];if(dependent.test(anchor.s)&&anchor.i>0)context.push(scored[anchor.i-1]);context.push(anchor);if(type==="Konu Özeti"&&words(anchor.s).length<12&&anchor.i+1<scored.length)context.push(scored[anchor.i+1]);for(const item of context){if(!item||selected.has(item.i))continue;const count=words(item.s).length;if(total+count>spec.words*1.15&&selected.size)continue;selected.set(item.i,item);total+=count}}const ordered=[...selected.values()].sort((a,b)=>a.i-b.i);if(!ordered.length)throw new Error("Özet oluşturmak için yeterli cümle bulunamadı.");return ordered}
function sectionDefinitions(type){if(type==="Yönetici Özeti")return[{head:"Amaç ve Genel Durum",rx:/amaç|kapsam|mevcut|durum|genel|kurum/i},{head:"Kritik Bulgular ve Kurumsal Etki",rx:/risk|etki|sorun|kritik|kayıp|maliyet|performans|güvenlik|sonuç/i},{head:"Öncelikler, Kararlar ve Öneriler",rx:/öncelik|karar|öner|gerek|aksiyon|eylem|plan|iyileştir|stratej|hedef/i}];if(type==="Toplantı Notları")return[{head:"Gündem",rx:/gündem|amaç|konu|toplantı/i},{head:"Görüşülen Konular",rx:/görüş|değerlendir|açıkla|bilgi|durum|tespit/i},{head:"Alınan Kararlar",rx:/karar|kararlaştır|onay|kabul|redded|sonuç/i},{head:"Aksiyonlar ve Sorumlular",rx:/aksiyon|eylem|sorumlu|atan|termin|tarih|takip|planlan|yapılacak/i}];if(type==="Ders Notları")return[{head:"Temel Kavramlar",rx:/tanım|kavram|ifade eder|denir|temel|özellik/i},{head:"Açıklamalar ve Bağlantılar",rx:/neden|nasıl|ilişki|süreç|aşama|yöntem|açıkla/i},{head:"Örnekler ve Uygulamalar",rx:/örnek|uygulama|senaryo|durumunda|örneğin/i},{head:"Önemli Sonuçlar",rx:/sonuç|özetle|önem|dikkat|unutulmamalı|çıkarım/i}];return[{head:"Konu ve Kapsam",rx:/konu|amaç|kapsam|hakkında|genel/i},{head:"Temel Noktalar",rx:/temel|önem|başlıca|özellik|durum|etki/i},{head:"Sonuç",rx:/sonuç|özetle|dolayısıyla|bu nedenle|öner/i}]}
function classifySections(items,type){const defs=sectionDefinitions(type),buckets=defs.map(d=>({...d,items:[]})),priority=type==="Yönetici Özeti"?[2,1,0]:type==="Toplantı Notları"?[3,2,0,1]:type==="Ders Notları"?[2,3,0,1]:[2,0,1];items.forEach((item,index)=>{let best=priority.find(i=>defs[i].rx.test(item.s));if(best===undefined){const pos=index/Math.max(1,items.length-1);if(type==="Toplantı Notları")best=1;else if(type==="Ders Notları")best=pos<.34?0:pos>.78?3:1;else best=pos<.3?0:pos>.72?defs.length-1:1}buckets[best].items.push(item.s)});return buckets.filter(b=>b.items.length)}
function paragraphGroups(items,targetCount){const desired=Math.max(1,Math.min(targetCount,items.length)),groups=[];let remaining=items.length;for(let i=0;i<desired;i++){const take=Math.ceil(remaining/(desired-i));groups.push(items.slice(items.length-remaining,items.length-remaining+take));remaining-=take}return groups.map(g=>normalize(g.join(" "))).filter(Boolean)}
function formatStructuredSummary(items,type,spec){const sections=classifySections(items,type),asList=type==="Toplantı Notları"||type==="Ders Notları";return sections.map(section=>{const heading=`<h2>${esc(section.head)}</h2>`;if(asList)return heading+`<ul>${section.items.map(s=>`<li>${esc(normalize(s))}</li>`).join("")}</ul>`;const sectionParagraphs=Math.max(1,Math.round(spec.p*section.items.length/items.length));return heading+paragraphGroups(section.items,sectionParagraphs).map(p=>`<p>${esc(p)}</p>`).join("")}).join("")}
const mailPatterns={request:/\bonay\b|\btalep\b|\bizin\b|uygun görüş|kaynak ihtiyac|bütçe ihtiyac/i,action:/aksiyon|eylem|sorumlu|termin|takip|\bplan\b|planı|planlan|hazırlanacak|hazırlayacak|hazırlayacaktır|uygulan|tamamlan|gerçekleştiril|yürütül/i,decision:/\bkarar\b|karara bağlan|kararlaştır|onaylan|kabul edil|reddedil/i,impact:/\brisk\b|\betki\b|\bkritik\b|\bmaliyet\b|\bbütçe\b|performans|hizmet sürekliliği|veri güvenliği|kapasite|kayıp|tehdit/i};
function classifyMailItems(items){const groups={overview:[],impact:[],decision:[],action:[],request:[]};for(const item of items){const s=normalize(item.s);if(mailPatterns.request.test(s))groups.request.push(s);else if(mailPatterns.decision.test(s))groups.decision.push(s);else if(mailPatterns.action.test(s))groups.action.push(s);else if(mailPatterns.impact.test(s))groups.impact.push(s);else groups.overview.push(s)}return groups}
function mailSubject(items,type,groups){let source=normalize((items[0]&&items[0].s)||"Belge değerlendirmesi").replace(/[.!?]+$/g,"").replace(/\s+(tespit edilmiştir|belirlenmiştir|değerlendirilmiştir|görülmüştür|devreye alınmıştır|giderilmiştir|tamamlanmıştır|sağlanmıştır|beklenmektedir|bulunmaktadır|oluşmaktadır)$/i,"");let short=words(source).slice(0,11).join(" ");if(!short)short="Belge değerlendirmesi";short=short.charAt(0).toLocaleUpperCase("tr-TR")+short.slice(1);const hasAction=groups.action.length||groups.decision.length,hasRequest=groups.request.length;if(type==="Yönetime Mail")return short+(hasRequest?" – Değerlendirme ve Onay Talebi":" – Yönetim Bilgilendirmesi");return short+(hasRequest?" – Değerlendirme Talebi":hasAction?" – Bilgilendirme ve Aksiyon":" – Bilgilendirme")}
function mailParagraphs(sentences,maxWords=72){const result=[];let current="";for(const sentence of sentences){const candidate=normalize((current+" "+sentence).trim());if(current&&words(candidate).length>maxWords){result.push(current);current=normalize(sentence)}else current=candidate}if(current)result.push(current);return result}
function mailParagraphHtml(sentences){return mailParagraphs(sentences).map(p=>`<p>${esc(p)}</p>`).join("")}
function mailListHtml(intro,sentences){return sentences.length?`<p>${esc(intro)}</p><ul>${sentences.map(s=>`<li>${esc(s)}</li>`).join("")}</ul>`:""}
function formatMailDraft(items,type){const upward=type==="Yönetime Mail",groups=classifyMailItems(items),subject=mailSubject(items,type,groups);let html=`<h2>E-posta Konusu</h2><p>${esc(subject)}</p><p>${upward?"Sayın Yöneticim,":"Değerli Çalışma Arkadaşlarım,"}</p>`;if(upward){html+=`<p>Konuya ilişkin mevcut durum ve öne çıkan hususlar aşağıda bilgilerinize sunulmuştur.</p>`;html+=mailParagraphHtml(groups.overview);html+=mailListHtml("Yapılan değerlendirmede öne çıkan bulgu ve etkiler:",groups.impact);html+=mailListHtml("Alınan kararlar:",groups.decision);html+=mailListHtml("Planlanan veya yürütülmesi gereken çalışmalar:",groups.action);html+=mailListHtml("Değerlendirme ve onay gerektiren hususlar:",groups.request);html+=`<p>${groups.request.length?"Belirtilen hususların değerlendirilerek uygun görülmesi hâlinde onaylanmasını arz ederim.":"Bilgilerinize arz ederim."}</p><p>Saygılarımla,</p>`}else{html+=mailParagraphHtml(groups.overview);html+=mailListHtml("Konuya ilişkin öne çıkan hususlar:",groups.impact);html+=mailListHtml("Alınan kararlar:",groups.decision);html+=mailListHtml("Gerçekleştirilmesi ve takip edilmesi gereken çalışmalar:",groups.action);html+=mailListHtml("Değerlendirilmesi gereken talepler:",groups.request);const operational=groups.action.length||groups.decision.length,closing=groups.request.length&&!operational?"Belirtilen talebin değerlendirilmesini ve sonucunun tarafıma iletilmesini rica ederim.":operational?"Belirtilen çalışmaların ilgili sorumlular tarafından yürütülmesini, gerekli koordinasyonun sağlanmasını ve gelişmeler hakkında tarafıma bilgi verilmesini rica ederim.":"Konuyu bilginize sunar, gerekli görülmesi hâlinde değerlendirmelerinizi rica ederim.";html+=`<p>${closing}</p><p>İyi çalışmalar,</p>`}html+=`<p>Muhammed Yusuf OLGUN</p>`;return html}
function buildSummary(text,type,spec){const items=selectSummaryItems(text,type,spec);return type==="Yönetici Maili"||type==="Yönetime Mail"?formatMailDraft(items,type):formatStructuredSummary(items,type,spec)}
function plainSummary(html){const d=document.createElement("div");d.innerHTML=html;return d.innerText.trim()}
function splitParagraph(text,limit=145){const sentences=String(text).match(/[^.!?]+[.!?]+|[^.!?]+$/g)||[text],chunks=[];let current="";for(const sentence of sentences){const clean=normalize(sentence),candidate=normalize((current+" "+clean).trim());if(current&&words(candidate).length>limit){chunks.push(current);current=clean}else current=candidate}if(current)chunks.push(current);return chunks.flatMap(chunk=>{const ws=chunk.split(/\s+/);if(ws.length<=limit)return[chunk];const parts=[];for(let i=0;i<ws.length;i+=limit)parts.push(ws.slice(i,i+limit).join(" "));return parts})}
function summaryBlocks(html){const root=document.createElement("div");root.innerHTML=html;const blocks=[];let section="",headingPending=false;[...root.children].forEach(node=>{const tag=node.tagName.toLowerCase();if(tag==="h2"){section=node.textContent.trim();headingPending=true;return}if(tag==="p"){splitParagraph(node.textContent).forEach((text,i)=>blocks.push({kind:"p",section,heading:headingPending&&i===0?section:"",text}));headingPending=false}else if(tag==="ul"){[...node.querySelectorAll(":scope > li")].forEach((li,i)=>blocks.push({kind:"li",section,heading:headingPending&&i===0?section:"",text:normalize(li.textContent)}));headingPending=false}});return blocks}
function renderBlockPage(blocks){let html="",listOpen=false,currentSection="";const closeList=()=>{if(listOpen){html+="</ul>";listOpen=false}};blocks.forEach((block,index)=>{if(block.heading){closeList();html+=`<h2>${esc(block.heading)}</h2>`;currentSection=block.section}else if(index===0&&block.section){closeList();html+=`<h2 class="continued">${esc(block.section)} (devam)</h2>`;currentSection=block.section}if(block.kind==="li"){if(!listOpen){html+="<ul>";listOpen=true}html+=`<li>${esc(block.text)}</li>`}else{closeList();html+=`<p>${esc(block.text)}</p>`}});closeList();return html}
function paginate(html){const blocks=summaryBlocks(html),pages=[];let page=[],count=0;for(const block of blocks){const n=words(block.text).length;if(page.length&&count+n>410){pages.push(renderBlockPage(page));page=[];count=0}page.push(block);count+=n}if(page.length)pages.push(renderBlockPage(page));return pages.length?pages:[html]}
function renderPages(){const chunks=paginate(state.summary);state.pages=chunks;el.paperWrap.innerHTML=chunks.map((pageHtml,i)=>`<article class="paper"><h1>${esc(el.summaryType.value)}</h1><div class="subtitle">${esc(state.fileName)} • ${new Intl.DateTimeFormat("tr-TR").format(new Date())}</div><div class="summary-text" contenteditable="true" spellcheck="true">${pageHtml}</div><div class="page-no">Sayfa ${i+1} / ${chunks.length}</div></article>`).join("");el.paperWrap.style.display="flex";el.summaryNotice.style.display="none";el.printBtn.disabled=false;el.wordBtn.disabled=false;el.summaryMeta.textContent=`${el.summaryType.value} • ${words(plainSummary(state.summary)).length.toLocaleString("tr-TR")} kelime • ${chunks.length} sayfa`;}
el.prepareBtn.addEventListener("click",async()=>{try{if(!state.source)throw new Error("Önce bir belge seçin veya metin yapıştırın.");const spec=limitSpec(el.summaryLimit.value),isMail=el.summaryType.value.includes("Mail");el.prepareBtn.disabled=true;progress(8,"Metin çözümleniyor");await sleep(60);progress(40,isMail?"Mail içeriği ve hitap dili hazırlanıyor":"Önemli bölümler belirleniyor");await sleep(60);state.summary=buildSummary(state.source,el.summaryType.value,spec);progress(86,"Yazım ve sayfa düzeni hazırlanıyor");renderPages();progress(100,isMail?"Mail taslağı hazır":"Özet hazır");toast(isMail?"Mail taslağı hazırlandı. Metni sayfa üzerinde düzenleyebilirsiniz.":"Özet hazırlandı. Metni sayfa üzerinde düzenleyebilirsiniz.")}catch(err){toast(err.message||String(err),6000)}finally{el.prepareBtn.disabled=false}});
function currentHtml(){return [...document.querySelectorAll(".summary-text")].map(x=>x.innerHTML).join("<div style='page-break-after:always'></div>")}
el.printBtn.addEventListener("click",()=>{if(window.Android&&typeof Android.printHtml==="function"){const html=exportDocument(currentHtml(),true);Android.printHtml(html,"Hulasa");return}window.print()});
el.wordBtn.addEventListener("click",()=>{const html="\ufeff"+exportDocument(currentHtml(),false),name=(state.fileName.replace(/\.[^.]+$/,"")||"Belge")+"_Hulasa.doc";if(window.Android&&typeof Android.saveTextFile==="function"){Android.saveTextFile(name,html,"application/msword;charset=utf-8");return}const a=document.createElement("a");a.href=URL.createObjectURL(new Blob([html],{type:"application/msword;charset=utf-8"}));a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(a.href),1500)});
function exportDocument(body,isPrint){return `<!doctype html><html lang="tr-TR" xml:lang="tr-TR" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word"><head><meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Content-Language" content="tr"><title>Hulasa</title><style>@page{size:A4 portrait;margin:20mm 18mm 22mm}body{font-family:Calibri,Arial,sans-serif;font-size:11pt;line-height:1.58;color:#0f172a;mso-ansi-language:TR}h1{font-size:18pt;margin:0 0 3pt}h2{font-size:13pt;line-height:1.25;margin:18pt 0 8pt;page-break-after:avoid}h2:first-of-type{margin-top:0}h2.continued{font-size:11.5pt;color:#475569}p{text-align:justify;margin:0 0 13pt}ul{margin:4pt 0 15pt 20pt;padding:0}li{margin:0 0 7pt;padding-left:3pt}.sub{color:#64748b;border-bottom:1px solid #cbd5e1;padding-bottom:8pt;margin-bottom:18pt}.foot{${isPrint?"position:fixed;bottom:5mm;left:0;right:0;":"mso-element:footer;"}text-align:center;color:#64748b;font-size:9pt}</style></head><body><h1>${esc(el.summaryType.value)}</h1><div class="sub">${esc(state.fileName)} • ${new Intl.DateTimeFormat("tr-TR").format(new Date())}</div>${body}<div class="foot">Sayfa <span style='mso-field-code:" PAGE "'></span> / <span style='mso-field-code:" NUMPAGES "'></span></div>${isPrint?'<script>addEventListener("load",()=>setTimeout(()=>print(),150));<\\/script>':""}</body></html>`}
</script>
</body>
</html>