mirror of
https://github.com/flatulentfowl/docdrop.git
synced 2026-07-22 06:49:52 +00:00
2 lines
11 KiB
JavaScript
2 lines
11 KiB
JavaScript
var g=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var D=(a,t)=>{for(var e in t)g(a,e,{get:t[e],enumerable:!0})},I=(a,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of P(t))!k.call(a,n)&&n!==e&&g(a,n,{get:()=>t[n],enumerable:!(s=A(t,n))||s.enumerable});return a};var b=a=>I(g({},"__esModule",{value:!0}),a);var E={};D(E,{default:()=>p});module.exports=b(E);var i=require("obsidian"),v=require("child_process"),T={executablePath:"markitdown",outputMode:"same",customOutputFolder:"",keepDataUris:!1,usePlugins:!1,openAiApiKey:"",openAiBaseUrl:"",openAiModel:"gpt-4o",useDocIntel:!1,docIntelEndpoint:"",docIntelApiKey:"",mimeType:"",charset:""},h=class extends i.Modal{constructor(t,e,s,n){super(t),this.fileName=e,this.onConfirm=s,this.onCancel=n}onOpen(){let{contentEl:t}=this;t.empty(),new i.Setting(t).setName("File already exists").setHeading(),t.createEl("p",{text:`"${this.fileName}" already exists. Overwrite it?`});let e=t.createDiv({cls:"modal-button-container"});e.createEl("button",{text:"Overwrite",cls:"mod-warning"}).addEventListener("click",()=>{this.close(),this.onConfirm()}),e.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{this.close(),this.onCancel()})}onClose(){this.contentEl.empty()}},m=class extends i.PluginSettingTab{constructor(t,e){super(t,e),this.plugin=e}display(){let{containerEl:t}=this;t.empty(),new i.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(s=>{this.plugin.settings.executablePath=s.trim()||"markitdown",this.plugin.saveSettings()})),new i.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(s=>{this.plugin.settings.outputMode=s,this.plugin.saveSettings(),this.display()})),this.plugin.settings.outputMode==="custom"&&new i.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(s=>{this.plugin.settings.customOutputFolder=s.trim(),this.plugin.saveSettings()})),new i.Setting(t).setName("Conversion").setHeading(),new i.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(s=>{this.plugin.settings.keepDataUris=s,this.plugin.saveSettings()})),new i.Setting(t).setName("MIME type hint").setDesc('Tells markitdown what kind of file it is receiving, e.g. "application/pdf". Normally not needed \u2014 markitdown detects the type automatically. Only set this if conversion produces wrong results and you suspect a detection failure.').addText(e=>e.setPlaceholder("application/pdf").setValue(this.plugin.settings.mimeType).onChange(s=>{this.plugin.settings.mimeType=s.trim(),this.plugin.saveSettings()})),new i.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(s=>{this.plugin.settings.charset=s.trim(),this.plugin.saveSettings()})),new i.Setting(t).setName("markitdown-ocr plugin (optional)").setHeading(),t.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 i.Setting(t).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(s=>{this.plugin.settings.usePlugins=s,this.plugin.saveSettings(),this.display()})),this.plugin.settings.usePlugins&&(new i.Setting(t).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(s=>{this.plugin.settings.openAiApiKey=s.trim(),this.plugin.saveSettings()}),e.inputEl.type="password"}),new i.Setting(t).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(s=>{this.plugin.settings.openAiModel=s.trim()||"gpt-4o",this.plugin.saveSettings()})),new i.Setting(t).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(s=>{this.plugin.settings.openAiBaseUrl=s.trim(),this.plugin.saveSettings()}))),new i.Setting(t).setName("Azure Document Intelligence (optional)").setHeading(),t.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 i.Setting(t).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(s=>{this.plugin.settings.useDocIntel=s,this.plugin.saveSettings(),this.display()})),this.plugin.settings.useDocIntel&&(new i.Setting(t).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(s=>{this.plugin.settings.docIntelEndpoint=s.trim(),this.plugin.saveSettings()})),new i.Setting(t).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(s=>{this.plugin.settings.docIntelApiKey=s.trim(),this.plugin.saveSettings()}),e.inputEl.type="password"}))}},p=class extends i.Plugin{async onload(){await this.loadSettings(),this.addSettingTab(new m(this.app,this)),this.addCommand({id:"convert-active-pdf",name:"Convert active PDF to Markdown",callback:()=>{let t=this.app.workspace.getActiveFile();if(!t){new i.Notice("No file is currently open.");return}if(t.extension.toLowerCase()!=="pdf"){new i.Notice("The active file is not a PDF.");return}this.convertPdf(t)}}),this.registerEvent(this.app.workspace.on("file-menu",(t,e)=>{e instanceof i.TFile&&e.extension.toLowerCase()==="pdf"&&t.addItem(s=>{s.setTitle("Convert to Markdown with DocDrop").setIcon("file-text").onClick(()=>this.convertPdf(e))})}))}onunload(){}async loadSettings(){this.settings=Object.assign({},T,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}async convertPdf(t){let e=this.app.vault.adapter;if(!(e instanceof i.FileSystemAdapter)){new i.Notice("DocDrop only works on desktop (local vault).");return}let n=`${e.getBasePath()}/${t.path}`,o=this.resolveOutputPath(t);if(o===null){new i.Notice(`Custom output folder "${this.settings.customOutputFolder}" does not exist in the vault.`);return}let r=this.app.vault.getAbstractFileByPath(o);if(r instanceof i.TFile){new h(this.app,o,()=>{this.runConversion(n,o,r)},()=>new i.Notice("Conversion cancelled.")).open();return}await this.runConversion(n,o,null)}resolveOutputPath(t){var o,r;let e=t.basename;if(this.settings.outputMode==="same"){let c=(r=(o=t.parent)==null?void 0:o.path)!=null?r:"",u=c==="/"?"":c;return u?`${u}/${e}.md`:`${e}.md`}let s=this.settings.customOutputFolder.replace(/\/$/,"");return s===""?`${e}.md`:this.app.vault.getAbstractFileByPath(s)instanceof i.TFolder?`${s}/${e}.md`:null}async runConversion(t,e,s){let n=new i.Notice(`Converting "${e}"\u2026`,0);try{let o=await this.invokeMarkItDown(t);n.hide(),s?await this.app.vault.modify(s,o):await this.app.vault.create(e,o),new i.Notice(`Converted: ${e}`,5e3)}catch(o){n.hide();let r=o instanceof Error?o.message:String(o);new i.Notice(`DocDrop failed:
|
|
${r}`,8e3),console.error("[DocDrop] conversion error",o)}}invokeMarkItDown(t){var r,c;let e=["/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"],s=[...(c=(r=process.env.PATH)==null?void 0:r.split(":"))!=null?c:[],...e].join(":"),n=[t];this.settings.keepDataUris&&n.push("--keep-data-uris"),this.settings.usePlugins&&n.push("--use-plugins"),this.settings.mimeType&&n.push("--mime-type",this.settings.mimeType),this.settings.charset&&n.push("--charset",this.settings.charset),this.settings.useDocIntel&&(n.push("--use-docintel"),this.settings.docIntelEndpoint&&n.push("--endpoint",this.settings.docIntelEndpoint));let o={PATH:s};return this.settings.useDocIntel&&this.settings.docIntelApiKey&&(o.AZURE_API_KEY=this.settings.docIntelApiKey),this.settings.usePlugins&&this.settings.openAiApiKey&&(o.OPENAI_API_KEY=this.settings.openAiApiKey),this.settings.usePlugins&&this.settings.openAiModel&&(o.OPENAI_MODEL=this.settings.openAiModel),this.settings.usePlugins&&this.settings.openAiBaseUrl&&(o.OPENAI_BASE_URL=this.settings.openAiBaseUrl),new Promise((u,f)=>{(0,v.execFile)(this.settings.executablePath,n,{encoding:"utf8",maxBuffer:50*1024*1024,env:{...process.env,...o}},(d,w,l)=>{if(d){let y=(l==null?void 0:l.trim())||d.message;f(new Error(`Exit code ${d.code}: ${y}`));return}l!=null&&l.trim()&&console.warn("[DocDrop] stderr:",l.trim()),u(w)})})}};
|