mirror of
https://github.com/flatulentfowl/docdrop.git
synced 2026-07-22 17:00:32 +00:00
2 lines
11 KiB
JavaScript
2 lines
11 KiB
JavaScript
var g=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var b=(a,t)=>{for(var i in t)g(a,i,{get:t[i],enumerable:!0})},I=(a,t,i,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of D(t))!k.call(a,e)&&e!==i&&g(a,e,{get:()=>t[e],enumerable:!(o=P(t,e))||o.enumerable});return a};var E=a=>I(g({},"__esModule",{value:!0}),a);var C={};b(C,{default:()=>p});module.exports=E(C);var n=require("obsidian"),w=require("child_process"),v=new Set(["pdf","docx","doc","pptx","ppt","xlsx","xls","jpg","jpeg","png","gif","webp","bmp","tiff","html","htm","csv","json","xml","epub","zip","mp3","wav"]),x={executablePath:"markitdown",outputMode:"same",customOutputFolder:"",keepDataUris:!1,usePlugins:!1,openAiApiKey:"",openAiBaseUrl:"",openAiModel:"gpt-4o",useDocIntel:!1,docIntelEndpoint:"",docIntelApiKey:"",charset:""},h=class extends n.Modal{constructor(t,i,o,e){super(t),this.fileName=i,this.onConfirm=o,this.onCancel=e}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:"File already exists"}),t.createEl("p",{text:`"${this.fileName}" already exists. Overwrite it?`});let i=t.createDiv({cls:"modal-button-container"});i.createEl("button",{text:"Overwrite",cls:"mod-warning"}).addEventListener("click",()=>{this.close(),this.onConfirm()}),i.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{this.close(),this.onCancel()})}onClose(){this.contentEl.empty()}},m=class extends n.PluginSettingTab{constructor(t,i){super(t,i),this.plugin=i}display(){let{containerEl:t}=this;t.empty(),t.createEl("h2",{text:"DocDrop"}),new n.Setting(t).setName("Executable path").setDesc('Full path to the markitdown binary, or just "markitdown" if it is on your PATH.').addText(e=>e.setPlaceholder("markitdown").setValue(this.plugin.settings.executablePath).onChange(async s=>{this.plugin.settings.executablePath=s.trim()||"markitdown",await this.plugin.saveSettings()})),new n.Setting(t).setName("Output location").setDesc("Where to save converted Markdown files.").addDropdown(e=>e.addOption("same","Same folder as PDF").addOption("custom","Custom folder").setValue(this.plugin.settings.outputMode).onChange(async s=>{this.plugin.settings.outputMode=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.outputMode==="custom"&&new n.Setting(t).setName("Custom output folder").setDesc('Vault-relative path, e.g. "Converted". The folder must already exist.').addText(e=>e.setPlaceholder("Converted").setValue(this.plugin.settings.customOutputFolder).onChange(async s=>{this.plugin.settings.customOutputFolder=s.trim(),await this.plugin.saveSettings()})),t.createEl("h3",{text:"Conversion options"}),new n.Setting(t).setName("Keep images").setDesc("Preserve images embedded in the PDF as base64 data inside the Markdown file. Turning this on makes the output file much larger but keeps all visuals inline. Leave off if you only need the text.").addToggle(e=>e.setValue(this.plugin.settings.keepDataUris).onChange(async s=>{this.plugin.settings.keepDataUris=s,await this.plugin.saveSettings()})),new n.Setting(t).setName("Character encoding hint").setDesc('Tells markitdown what text encoding the file uses, e.g. "UTF-8" or "ISO-8859-1". Leave blank unless converted text contains garbled characters \u2014 markitdown detects encoding automatically.').addText(e=>e.setPlaceholder("UTF-8").setValue(this.plugin.settings.charset).onChange(async s=>{this.plugin.settings.charset=s.trim(),await this.plugin.saveSettings()}));let i=t.createEl("details",{cls:"docdrop-collapsible"});i.open=this.plugin.settings.usePlugins,i.createEl("summary",{text:"markitdown-ocr plugin (optional)"}),i.createEl("p",{text:"markitdown-ocr is a free, separately-installed plugin that uses an AI vision model (like OpenAI's GPT-4o) to read text from images inside PDFs \u2014 useful for scanned documents or PDFs that are just pictures of pages. Install it first with: pip install markitdown-ocr. You will need an OpenAI account (or a compatible service) to provide the AI.",cls:"setting-item-description"}),new n.Setting(i).setName("Enable markitdown-ocr").setDesc("Activate any installed markitdown plugins, including markitdown-ocr. Has no effect if markitdown-ocr is not installed. Install it with: pip install markitdown-ocr").addToggle(e=>e.setValue(this.plugin.settings.usePlugins).onChange(async s=>{this.plugin.settings.usePlugins=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.usePlugins&&(new n.Setting(i).setName("OpenAI API key").setDesc("Your secret API key from OpenAI (or a compatible service). Required for markitdown-ocr to call the AI vision model. Get one at platform.openai.com \u2192 API keys. Looks like: sk-proj-...").addText(e=>{e.setPlaceholder("sk-proj-...").setValue(this.plugin.settings.openAiApiKey).onChange(async s=>{this.plugin.settings.openAiApiKey=s.trim(),await this.plugin.saveSettings()}),e.inputEl.type="password"}),new n.Setting(i).setName("AI model").setDesc('The vision-capable AI model markitdown-ocr will use to read images. Must support image/vision input. "gpt-4o" is the default and works well. Other options: gpt-4o-mini (cheaper, slightly less accurate).').addText(e=>e.setPlaceholder("gpt-4o").setValue(this.plugin.settings.openAiModel).onChange(async s=>{this.plugin.settings.openAiModel=s.trim()||"gpt-4o",await this.plugin.saveSettings()})),new n.Setting(i).setName("OpenAI API base URL (optional)").setDesc("Override the API server markitdown-ocr connects to. Leave blank to use the default OpenAI servers. Set this if you are using Azure OpenAI or a self-hosted compatible service (e.g. https://your-resource.openai.azure.com/).").addText(e=>e.setPlaceholder("https://api.openai.com/v1").setValue(this.plugin.settings.openAiBaseUrl).onChange(async s=>{this.plugin.settings.openAiBaseUrl=s.trim(),await this.plugin.saveSettings()})));let o=t.createEl("details",{cls:"docdrop-collapsible"});o.open=this.plugin.settings.useDocIntel,o.createEl("summary",{text:"Azure Document Intelligence (optional)"}),o.createEl("p",{text:"Document Intelligence is a paid Microsoft Azure cloud service that uses AI to read PDFs with much higher accuracy than offline conversion \u2014 especially for scanned documents, handwriting, tables, and complex layouts. Requires an Azure account. Leave these blank to use free offline conversion instead.",cls:"setting-item-description"}),new n.Setting(o).setName("Use Document Intelligence").setDesc("Send the PDF to Microsoft Azure for conversion instead of processing it locally. Produces better results for scanned or image-based PDFs, but requires internet access and a paid Azure subscription.").addToggle(e=>e.setValue(this.plugin.settings.useDocIntel).onChange(async s=>{this.plugin.settings.useDocIntel=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.useDocIntel&&(new n.Setting(o).setName("Endpoint URL").setDesc("The URL of your Azure Document Intelligence resource. Find it in the Azure Portal: open your Document Intelligence resource \u2192 Keys and Endpoint. Looks like: https://your-resource-name.cognitiveservices.azure.com/").addText(e=>e.setPlaceholder("https://your-resource.cognitiveservices.azure.com/").setValue(this.plugin.settings.docIntelEndpoint).onChange(async s=>{this.plugin.settings.docIntelEndpoint=s.trim(),await this.plugin.saveSettings()})),new n.Setting(o).setName("API key").setDesc("The secret key that authenticates you with Azure Document Intelligence. Find it in the Azure Portal: open your Document Intelligence resource \u2192 Keys and Endpoint \u2192 KEY 1 or KEY 2. Either key works.").addText(e=>{e.setPlaceholder("Your Azure API key").setValue(this.plugin.settings.docIntelApiKey).onChange(async s=>{this.plugin.settings.docIntelApiKey=s.trim(),await this.plugin.saveSettings()}),e.inputEl.type="password"}))}},p=class extends n.Plugin{async onload(){await this.loadSettings(),this.addSettingTab(new m(this.app,this)),this.addCommand({id:"convert-active-file",name:"Convert active file to Markdown",callback:()=>{let t=this.app.workspace.getActiveFile();if(!t){new n.Notice("No file is currently open.");return}if(!v.has(t.extension.toLowerCase())){new n.Notice(`DocDrop does not support .${t.extension} files.`);return}this.convertFile(t)}}),this.registerEvent(this.app.workspace.on("file-menu",(t,i)=>{i instanceof n.TFile&&v.has(i.extension.toLowerCase())&&t.addItem(o=>{o.setTitle("Convert to Markdown with DocDrop").setIcon("file-text").onClick(()=>this.convertFile(i))})}))}onunload(){}async loadSettings(){this.settings=Object.assign({},x,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}async convertFile(t){let e=`${this.app.vault.adapter.getBasePath()}/${t.path}`,s=this.resolveOutputPath(t);if(s===null){new n.Notice(`Custom output folder "${this.settings.customOutputFolder}" does not exist in the vault.`);return}let r=this.app.vault.getAbstractFileByPath(s);if(r instanceof n.TFile){new h(this.app,s,()=>this.runConversion(e,s,r),()=>new n.Notice("Conversion cancelled.")).open();return}await this.runConversion(e,s,null)}resolveOutputPath(t){var s,r;let i=t.basename;if(this.settings.outputMode==="same"){let c=(r=(s=t.parent)==null?void 0:s.path)!=null?r:"",u=c==="/"?"":c;return u?`${u}/${i}.md`:`${i}.md`}let o=this.settings.customOutputFolder.replace(/\/$/,"");return o===""?`${i}.md`:this.app.vault.getAbstractFileByPath(o)instanceof n.TFolder?`${o}/${i}.md`:null}async runConversion(t,i,o){let e=new n.Notice(`Converting "${i}"\u2026`,0);try{let s=await this.invokeMarkItDown(t);e.hide(),o?await this.app.vault.modify(o,s):await this.app.vault.create(i,s),new n.Notice(`Converted: ${i}`,5e3)}catch(s){e.hide();let r=s instanceof Error?s.message:String(s);new n.Notice(`DocDrop failed:
|
|
${r}`,8e3),console.error("[DocDrop] conversion error",s)}}invokeMarkItDown(t){var r,c;let i=["/usr/local/bin","/opt/homebrew/bin","/opt/homebrew/sbin","/Library/Frameworks/Python.framework/Versions/3.12/bin","/Library/Frameworks/Python.framework/Versions/3.11/bin","/Library/Frameworks/Python.framework/Versions/3.10/bin","/usr/bin","/bin"],o=[...(c=(r=process.env.PATH)==null?void 0:r.split(":"))!=null?c:[],...i].join(":"),e=[t];this.settings.keepDataUris&&e.push("--keep-data-uris"),this.settings.usePlugins&&e.push("--use-plugins"),this.settings.charset&&e.push("--charset",this.settings.charset),this.settings.useDocIntel&&(e.push("--use-docintel"),this.settings.docIntelEndpoint&&e.push("--endpoint",this.settings.docIntelEndpoint));let s={PATH:o};return this.settings.useDocIntel&&this.settings.docIntelApiKey&&(s.AZURE_API_KEY=this.settings.docIntelApiKey),this.settings.usePlugins&&this.settings.openAiApiKey&&(s.OPENAI_API_KEY=this.settings.openAiApiKey),this.settings.usePlugins&&this.settings.openAiModel&&(s.OPENAI_MODEL=this.settings.openAiModel),this.settings.usePlugins&&this.settings.openAiBaseUrl&&(s.OPENAI_BASE_URL=this.settings.openAiBaseUrl),new Promise((u,f)=>{(0,w.execFile)(this.settings.executablePath,e,{encoding:"utf8",maxBuffer:50*1024*1024,env:{...process.env,...s}},(d,y,l)=>{if(d){let A=(l==null?void 0:l.trim())||d.message;f(new Error(`Exit code ${d.code}: ${A}`));return}l!=null&&l.trim()&&console.warn("[DocDrop] stderr:",l.trim()),u(y)})})}};
|