mirror of
https://github.com/snoopyrain/obvec-sync.git
synced 2026-07-22 14:30:24 +00:00
- Use activeWindow.setTimeout/setInterval for popout window compatibility
- Replace createEl('div') with createDiv() shorthand
- Type loadData() return as Partial<ObvecSettings> to avoid unsafe any
- Remove unnecessary console.debug logging
- Replace builtin-modules package with native node:module builtinModules
10 lines
11 KiB
JavaScript
10 lines
11 KiB
JavaScript
var P=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var K=(o,l)=>{for(var t in l)P(o,t,{get:l[t],enumerable:!0})},U=(o,l,t,s)=>{if(l&&typeof l=="object"||typeof l=="function")for(let e of M(l))!C.call(o,e)&&e!==t&&P(o,e,{get:()=>l[e],enumerable:!(s=F(l,e))||s.enumerable});return o};var H=o=>U(P({},"__esModule",{value:!0}),o);var L={};K(L,{default:()=>$});module.exports=H(L);var n=require("obsidian"),k={apiKey:"",serverUrl:"https://rag.10xboost.org",autoSync:!0,syncIntervalMinutes:15,excludePatterns:""};async function A(o){let t=new TextEncoder().encode(o),s=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(s)).map(i=>i.toString(16).padStart(2,"0")).join("")}var $=class extends n.Plugin{constructor(){super(...arguments);this.settings=k;this.statusBarEl=null;this.syncInterval=null;this.isSyncing=!1;this.lastSyncedHashes=new Map;this.serverStats=null}async onload(){await this.loadSettings(),this.statusBarEl=this.addStatusBarItem(),this.statusBarEl.addClass("obvec-status"),this.updateStatusBar("idle"),this.addSettingTab(new E(this.app,this)),this.addCommand({id:"sync-now",name:"Sync vault now",callback:()=>{this.syncAll()}}),this.addCommand({id:"sync-status",name:"View sync status",callback:()=>{this.showSyncStatus()}}),this.registerEvent(this.app.vault.on("modify",(0,n.debounce)(t=>{this.settings.autoSync&&this.settings.apiKey&&this.syncFile(t,"upsert")},5e3,!0))),this.registerEvent(this.app.vault.on("create",t=>{t instanceof n.TFile&&this.settings.autoSync&&this.settings.apiKey&&this.syncFile(t,"upsert")})),this.registerEvent(this.app.vault.on("delete",t=>{t instanceof n.TFile&&t.extension==="md"&&this.settings.autoSync&&this.settings.apiKey&&this.syncDeletedFile(t.path)})),this.registerEvent(this.app.vault.on("rename",(t,s)=>{t instanceof n.TFile&&t.extension==="md"&&this.settings.autoSync&&this.settings.apiKey&&(this.syncDeletedFile(s),this.syncFile(t,"upsert"))})),this.startPeriodicSync(),this.settings.apiKey&&(this.fetchServerStats(),this.settings.autoSync&&activeWindow.setTimeout(()=>{this.syncAll()},1e4))}onunload(){this.stopPeriodicSync()}async loadSettings(){let t=await this.loadData();this.settings=Object.assign({},k,t!=null?t:{})}async saveSettings(){await this.saveData(this.settings),this.stopPeriodicSync(),this.startPeriodicSync()}startPeriodicSync(){if(!this.settings.autoSync||!this.settings.apiKey)return;let t=this.settings.syncIntervalMinutes*60*1e3;this.syncInterval=activeWindow.setInterval(()=>{this.syncAll()},t),this.registerInterval(this.syncInterval)}stopPeriodicSync(){this.syncInterval!==null&&(activeWindow.clearInterval(this.syncInterval),this.syncInterval=null)}updateStatusBar(t,s){var i,a;if(!this.statusBarEl)return;let e=(a=(i=this.serverStats)==null?void 0:i.vault_file_count)!=null?a:this.lastSyncedHashes.size;switch(t){case"idle":this.statusBarEl.setText(`Obvec: ${e} indexed`);break;case"syncing":this.statusBarEl.setText(`Obvec: syncing ${s||""}...`);break;case"error":this.statusBarEl.setText("Obvec: error");break}}async fetchServerStats(){if(!this.settings.apiKey)return null;try{let t=await(0,n.requestUrl)({url:`${this.settings.serverUrl}/api/user/stats`,method:"GET",headers:{Authorization:`Bearer ${this.settings.apiKey}`}});return t.status===200?(this.serverStats=t.json,this.updateStatusBar("idle"),this.serverStats):null}catch(t){return console.warn("Obvec: stats fetch failed",t),null}}shouldExclude(t){if(!this.settings.excludePatterns.trim())return!1;let s=this.settings.excludePatterns.split(`
|
|
`).map(e=>e.trim()).filter(Boolean);for(let e of s)try{if(new RegExp(e).test(t))return!0}catch(i){}return!1}async syncFile(t,s){if(t.extension==="md"&&!this.isSyncing&&!this.shouldExclude(t.path)&&this.settings.apiKey)try{let e=await this.app.vault.read(t),i=await A(e);if(this.lastSyncedHashes.get(t.path)===i)return;await this.sendSyncBatch([{file_path:t.path,content:e,content_hash:i,action:s}]),this.lastSyncedHashes.set(t.path,i),await this.fetchServerStats()}catch(e){console.error("Obvec sync error:",e)}}async syncDeletedFile(t){if(!(!this.settings.apiKey||this.isSyncing))try{await this.sendSyncBatch([{file_path:t,content:"",content_hash:"",action:"delete"}]),this.lastSyncedHashes.delete(t),await this.fetchServerStats()}catch(s){console.error("Obvec delete sync error:",s)}}async syncAll(){var s,e,i;if(this.isSyncing)return;if(!this.settings.apiKey){new n.Notice("Obvec: please set your API key in settings");return}this.isSyncing=!0;let t=Date.now();try{let a=this.app.vault.getMarkdownFiles(),h=a.length;this.updateStatusBar("syncing",`scanning 0/${h}`);let r=[],p=0;for(let c of a){if(this.shouldExclude(c.path)){p++;continue}let f=await this.app.vault.read(c),m=await A(f);this.lastSyncedHashes.get(c.path)!==m&&r.push({file_path:c.path,content:f,content_hash:m,action:"upsert"}),p++,p%100===0&&this.updateStatusBar("syncing",`scanning ${p}/${h}`)}if(r.length===0){await this.fetchServerStats(),this.updateStatusBar("idle"),this.isSyncing=!1,new n.Notice(`Obvec: all ${h} files up to date`);return}let w=Math.ceil(r.length/3*3/60);new n.Notice(`Obvec: uploading ${r.length} files (~${w} min)`);let v=3,I=3e3,x=0,B=0,S=[],b=0,_=!1;for(let c=0;c<r.length&&!_;c+=v){let f=r.slice(c,c+v);b+=f.length,this.updateStatusBar("syncing",`${b}/${r.length} uploading`);let m=!1;for(let u=0;u<3&&!m;u++)try{if(u>0){let d=5e3*u;await new Promise(g=>activeWindow.setTimeout(g,d)),this.updateStatusBar("syncing",`${b}/${r.length} retry ${u}`)}let y=await this.sendSyncBatch(f);x+=y.synced,B+=y.skipped,S.push(...y.errors);for(let d of f)y.errors.some(g=>g.includes(d.file_path))||this.lastSyncedHashes.set(d.file_path,d.content_hash);m=!0}catch(y){let d=y instanceof Error?y.message:String(y),g=(s=d.match(/\d+/))==null?void 0:s[0];if(g==="429"||g==="401"){console.error(`Obvec: ${g==="429"?"rate limited":"auth failed"} at batch ${c/v+1}, stopping sync`),S.push(`${g==="429"?"Rate limited":"Auth failed"} after ${b} files \u2014 will resume next sync`),_=!0;break}console.error(`Obvec batch ${c/v+1} attempt ${u+1} failed:`,d),g==="500"&&u<2&&await new Promise(D=>activeWindow.setTimeout(D,1e4)),u===2&&S.push(`Batch ${c/v+1}: ${d}`)}c+v<r.length&&!_&&await new Promise(u=>activeWindow.setTimeout(u,I))}await this.fetchServerStats();let O=((Date.now()-t)/1e3).toFixed(1),T=(i=(e=this.serverStats)==null?void 0:e.vault_file_count)!=null?i:"?";S.length>0?(new n.Notice(`Obvec: ${x} synced, ${S.length} errors (${O}s)
|
|
Total indexed: ${T}`),console.error("Obvec sync errors:",S)):new n.Notice(`Obvec: ${x} synced, ${B} unchanged (${O}s)
|
|
Total indexed: ${T}`),this.updateStatusBar("idle")}catch(a){let h=a instanceof Error?a.message:String(a);console.error("Obvec sync error:",h),this.updateStatusBar("error"),new n.Notice(`Obvec sync failed: ${h}`)}finally{this.isSyncing=!1}}async sendSyncBatch(t){let s=`${this.settings.serverUrl}/api/sync/batch`,e=await(0,n.requestUrl)({url:s,method:"POST",headers:{Authorization:`Bearer ${this.settings.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({files:t})});if(e.status!==200)throw new Error(`Request failed, status ${e.status}`);return e.json}async showSyncStatus(){if(!this.settings.apiKey){new n.Notice("Obvec: please set your API key in settings");return}try{let t=await this.fetchServerStats();if(!t){new n.Notice("Obvec: failed to fetch status");return}let s=this.app.vault.getMarkdownFiles().length,e=t.plan==="pro"?"1GB":"10MB";new n.Notice(`Obvec status
|
|
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
Plan: ${t.plan.toUpperCase()}
|
|
Indexed: ${t.vault_file_count} / ${s} files
|
|
Chunks: ${t.vault_chunk_count}
|
|
Quota: ${e}
|
|
Last sync: ${t.last_sync_at?new Date(t.last_sync_at+"Z").toLocaleString():"Never"}`,1e4)}catch(t){let s=t instanceof Error?t.message:String(t);new n.Notice(`Obvec: failed to get status - ${s}`)}}},E=class extends n.PluginSettingTab{constructor(t,s){super(t,s);this.statsEl=null;this.plugin=s}display(){let{containerEl:t}=this;t.empty(),new n.Setting(t).setName("Obvec \u2014 AI search for your second brain").setHeading();let s=t.createDiv({cls:"obvec-stats-panel"});this.statsEl=s,this.refreshStatsPanel(s),new n.Setting(t).setName("API key").setDesc("Available on your dashboard").addText(e=>e.setPlaceholder("Paste your API key here").setValue(this.plugin.settings.apiKey).onChange(async i=>{this.plugin.settings.apiKey=i.replace(/\s/g,""),await this.plugin.saveSettings()})),new n.Setting(t).setName("Server URL").setDesc("Obvec server URL").addText(e=>e.setValue(this.plugin.settings.serverUrl).onChange(async i=>{this.plugin.settings.serverUrl=i.trim().replace(/\/$/,""),await this.plugin.saveSettings()})),new n.Setting(t).setName("Auto sync").setDesc("Automatically sync when files are modified").addToggle(e=>e.setValue(this.plugin.settings.autoSync).onChange(async i=>{this.plugin.settings.autoSync=i,await this.plugin.saveSettings()})),new n.Setting(t).setName("Sync interval").setDesc("Full vault sync interval in minutes").addDropdown(e=>e.addOption("5","5 minutes").addOption("15","15 minutes").addOption("30","30 minutes").addOption("60","60 minutes").setValue(String(this.plugin.settings.syncIntervalMinutes)).onChange(async i=>{this.plugin.settings.syncIntervalMinutes=parseInt(i),await this.plugin.saveSettings()})),new n.Setting(t).setName("Exclude patterns").setDesc("Regex patterns to exclude files (one per line)").addTextArea(e=>e.setPlaceholder("").setValue(this.plugin.settings.excludePatterns).onChange(async i=>{this.plugin.settings.excludePatterns=i,await this.plugin.saveSettings()})),new n.Setting(t).setName("Sync now").setDesc("Manually trigger a full vault sync").addButton(e=>e.setButtonText("Sync now").setCta().onClick(()=>{this.plugin.syncAll(),activeWindow.setTimeout(()=>this.refreshStatsPanel(s),3e3)})),new n.Setting(t).setName("Refresh stats").setDesc("Fetch latest index count from server").addButton(e=>e.setButtonText("Refresh").onClick(async()=>{await this.plugin.fetchServerStats(),this.refreshStatsPanel(s),new n.Notice("Obvec: stats refreshed")}))}refreshStatsPanel(t){t.empty();let s=this.plugin.serverStats,e=this.plugin.app.vault.getMarkdownFiles().length;if(!this.plugin.settings.apiKey){t.createEl("p",{text:"Enter your API key above to get started.",cls:"setting-item-description"});return}if(!s){t.createEl("p",{text:"Loading stats...",cls:"setting-item-description"}),this.plugin.fetchServerStats().then(()=>this.refreshStatsPanel(t));return}let i=t.createDiv({cls:"obvec-stats-grid"}),a=(r,p)=>{let w=i.createDiv({cls:"obvec-stat-card"});w.createDiv({text:p,cls:"obvec-stat-value"}),w.createDiv({text:r,cls:"obvec-stat-label setting-item-description"})};a("Indexed",`${s.vault_file_count} / ${e}`),a("Chunks",`${s.vault_chunk_count}`),a("Plan",s.plan.toUpperCase());let h=e-s.vault_file_count;if(h>0){let r=Math.ceil(h/3*3/60);t.createEl("p",{text:`${h} files to sync (~${r} min)`,cls:"obvec-sync-estimate setting-item-description"})}s.last_sync_at&&t.createEl("p",{text:`Last sync: ${new Date(s.last_sync_at+"Z").toLocaleString()}`,cls:"obvec-last-sync setting-item-description"})}};
|