From d8dc82362b59eaa818ebc8aed1befa49a724c267 Mon Sep 17 00:00:00 2001 From: wujunchen Date: Sun, 26 Apr 2026 18:10:17 +0800 Subject: [PATCH] feat: support cancellable batch generation Change-Id: I89302f58995e4e15fbf77c20c87a94832e572d20 --- main.js | 44 +++++++++++++++---------------- main.ts | 61 ++++++++++++++++++++++++++++++++++++------- src/batch.ts | 29 ++++++++++++++++++-- src/i18n.ts | 10 +++++-- tests/main.test.js | 1 + tests/modules.test.js | 15 +++++++++-- 6 files changed, 122 insertions(+), 38 deletions(-) diff --git a/main.js b/main.js index 4c2853a..8b32729 100644 --- a/main.js +++ b/main.js @@ -1,24 +1,24 @@ 'use strict'; -"use strict";var xt=Object.create;var ie=Object.defineProperty;var At=Object.getOwnPropertyDescriptor;var Pt=Object.getOwnPropertyNames;var St=Object.getPrototypeOf,Tt=Object.prototype.hasOwnProperty;var Et=(r,t)=>{for(var e in t)ie(r,e,{get:t[e],enumerable:!0})},tt=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Pt(t))!Tt.call(r,i)&&i!==e&&ie(r,i,{get:()=>t[i],enumerable:!(n=At(t,i))||n.enumerable});return r};var ae=(r,t,e)=>(e=r!=null?xt(St(r)):{},tt(t||!r||!r.__esModule?ie(e,"default",{value:r,enumerable:!0}):e,r)),kt=r=>tt(ie({},"__esModule",{value:!0}),r);var ir={};Et(ir,{__test:()=>nr,default:()=>rr});module.exports=kt(ir);var p=require("obsidian");function J(r,t){if(!t)return-1;let e=m=>m.replace(/\s+/g," ").trim(),n=m=>{let g=[],d=[],f=!1;for(let y=0;y0;continue}f&&(g.push(" "),d.push(y),f=!1),g.push(_),d.push(y)}return{text:g.join(""),map:d}},i=m=>{if(!m)return-1;let g=r.indexOf(m);if(g===-1)return-1;let d=0;for(let f=0;f=0||(a=i(t.trim()),a>=0))return a;for(let m of[60,40,25,15]){let g=t.trim().slice(0,m);if(a=i(g),a>=0)return a}let s=n(r),o=e(t).slice(0,30);if(!o)return-1;let l=s.text.indexOf(o);if(l===-1)return-1;let c=s.map[l];if(c==null)return-1;let u=0;for(let m=0;m{for(var e in t)ie(r,e,{get:t[e],enumerable:!0})},at=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Et(t))!Rt.call(r,i)&&i!==e&&ie(r,i,{get:()=>t[i],enumerable:!(n=kt(t,i))||n.enumerable});return r};var ae=(r,t,e)=>(e=r!=null?Tt(Ft(r)):{},at(t||!r||!r.__esModule?ie(e,"default",{value:r,enumerable:!0}):e,r)),Nt=r=>at(ie({},"__esModule",{value:!0}),r);var cr={};Mt(cr,{__test:()=>or,default:()=>sr});module.exports=Nt(cr);var d=require("obsidian");function J(r,t){if(!t)return-1;let e=h=>h.replace(/\s+/g," ").trim(),n=h=>{let g=[],p=[],f=!1;for(let y=0;y0;continue}f&&(g.push(" "),p.push(y),f=!1),g.push(B),p.push(y)}return{text:g.join(""),map:p}},i=h=>{if(!h)return-1;let g=r.indexOf(h);if(g===-1)return-1;let p=0;for(let f=0;f=0||(a=i(t.trim()),a>=0))return a;for(let h of[60,40,25,15]){let g=t.trim().slice(0,h);if(a=i(g),a>=0)return a}let s=n(r),o=e(t).slice(0,30);if(!o)return-1;let c=s.text.indexOf(o);if(c===-1)return-1;let l=s.map[c];if(l==null)return-1;let u=0;for(let h=0;h --version` \u9A8C\u8BC1 spawn \u80FD\u627E\u5230\u4E8C\u8FDB\u5236",backendTestFailed:"\u2717 \u540E\u7AEF\u6D4B\u8BD5\u5931\u8D25\uFF1A{error}",settingExportFolderName:"\u5BFC\u51FA\u6587\u4EF6\u5939",settingExportFolderDesc:"\u5BF9\u7167\u7B14\u8BB0\u751F\u6210\u4F4D\u7F6E\uFF08\u76F8\u5BF9 Vault \u6839\uFF09",cacheHeader:"\u7F13\u5B58",settingMaxCacheName:"\u6700\u5927\u7F13\u5B58\u7BC7\u6570",settingMaxCacheDesc:"\u8D85\u8FC7\u4E0A\u9650\u540E\u6309\u6700\u8FD1\u8BBF\u95EE\u65F6\u95F4\u6DD8\u6C70\u6700\u65E7\u7684\u7B14\u8BB0\u7F13\u5B58\uFF1B\u7F13\u5B58\u4FDD\u5B58\u5728\u63D2\u4EF6\u76EE\u5F55\u7684 cache.json",cachePruned:"\u5DF2\u6DD8\u6C70 {count} \u6761\u65E7\u7F13\u5B58",cachedNotesName:"\u5DF2\u7F13\u5B58\u7B14\u8BB0\uFF1A{count} \u7BC7",cachedNotesDesc:"\u7F13\u5B58\u4EE5\u6E90\u7B14\u8BB0 SHA1 + \u751F\u6210\u914D\u7F6E\u6307\u7EB9\u4F5C\u4E3A\u5931\u6548\u952E\uFF0C\u6E90\u7B14\u8BB0\u6216\u6A21\u578B\u914D\u7F6E\u4FEE\u6539\u540E\u4F1A\u663E\u793A stale \u63D0\u793A",clearAllCacheButton:"\u6E05\u9664\u6240\u6709\u7F13\u5B58",settingStreamingName:"\u6D41\u5F0F\u8F93\u51FA",settingStreamingDesc:"\u542F\u7528\u540E\u751F\u6210\u65F6\u5B9E\u65F6\u663E\u793A LLM \u8F93\u51FA\u8FDB\u5EA6\uFF08\u4EC5 OpenAI Chat \u548C Anthropic \u683C\u5F0F\u652F\u6301\uFF09",cmdBatchGenerate:"\u6279\u91CF\u751F\u6210\u5BF9\u7167\u7B14\u8BB0\uFF08\u5F53\u524D\u6587\u4EF6\u5939\uFF09",batchSelectFolder:"\u8BF7\u8F93\u5165\u8981\u6279\u91CF\u5904\u7406\u7684\u6587\u4EF6\u5939\u8DEF\u5F84\uFF08\u7559\u7A7A\u4E3A Vault \u6839\u76EE\u5F55\uFF09",batchFolderPrompt:"\u6587\u4EF6\u5939\u8DEF\u5F84",batchNoMarkdown:"\u8BE5\u6587\u4EF6\u5939\u4E0B\u6CA1\u6709 Markdown \u6587\u4EF6",batchProgress:"\u6279\u91CF\u751F\u6210\uFF1A{current}/{total}",batchDone:"\u6279\u91CF\u751F\u6210\u5B8C\u6210\uFF1A\u5171\u5904\u7406 {total} \u7BC7\uFF0C\u8DF3\u8FC7\u7F13\u5B58 {skipped} \u7BC7"},en:{appTitle:"Parallel Reader",settingsTitle:"Parallel Reader Settings",emptyOpenNote:"Open a note, then run:",emptyNoCache:"This note has no cached parallel notes. Run:",commandGenerate:"Parallel Reader: Generate notes for current note",displayName:"Parallel Reader",ribbonOpen:"Open Parallel Reader pane",cmdRun:"Generate parallel notes for current note (cache first)",cmdRegen:"Regenerate parallel notes (bypass cache)",cmdOpenView:"Open Parallel Reader pane",cmdExport:"Export current parallel notes to Vault",cmdCopyMarkdown:"Copy current parallel notes as Markdown",cmdCancel:"Cancel current parallel-note generation",cmdClearCurrent:"Clear cache for current note",cmdClearAll:"Clear all caches",cmdCardPrev:"Focus previous summary card",cmdCardNext:"Focus next summary card",cmdCardJump:"Jump current summary card to source",actionCancel:"Cancel generation",actionRegenerate:"Regenerate",actionCopyAll:"Copy all Markdown",actionExport:"Export to Vault",staleBanner:"The source note or generation settings changed. This is stale cache.",loadingDefault:"Generating parallel notes...",loadingGenerating:"Parallel Reader: asking the LLM to read and segment the full note...",loadingSubtitle:"You can keep reading. Cards will refresh when generation finishes.",errorTitle:"Generation failed",emptyCard:"(not generated)",anchorMismatch:"Anchor did not match; scroll sync is unavailable",menuCopyMarkdown:"Copy Markdown",menuCopyPlain:"Copy plain text",menuCopyAnchor:"Copy anchor quote",menuJumpSource:"Jump to source",menuEditCard:"Edit this card",menuDeleteCard:"Delete this card",copiedMarkdown:"Copied Markdown",copiedPlain:"Copied plain text",copiedAnchor:"Copied anchor quote",copiedAllMarkdown:"Copied all Markdown",copyFailed:"Copy failed: {error}",actionFailed:"{label} failed: {error}",exported:"Exported \u2192 {path}",noCurrentNote:"No current note",cacheClearedFile:"Cleared cache: {name}",cacheClearedAll:"Cleared {count} cache entries",noCancelableJob:"No cancellable generation job",cancelRequested:"Cancel requested",cancelRequestedApiInFlight:"Cancel requested. The in-flight API request cannot be aborted immediately; its result will be ignored.",fileMenuGenerate:"Generate parallel notes",fileMenuRegen:"Regenerate parallel notes",fileMenuClear:"Clear parallel-note cache",noExportContent:"No parallel notes to export",noCopyContent:"No parallel notes to copy",noActiveCard:"No active summary card to jump",viewOpenFailed:"Could not open the Parallel Reader pane",confirmRegenerateEditedCards:"These parallel-reader cards were edited manually. Regenerating will overwrite those edits. Continue?",regenerateCancelled:"Regeneration cancelled",cardDeleted:"Deleted this card",cardSaved:"Saved this card",editCardTitle:"Edit summary card",editCardTitleField:"Title",editCardGistField:"Gist",editCardBulletsField:"Bullets (one per line)",editCardCancel:"Cancel",editCardSave:"Save",openNoteFirst:"Open a note first",alreadyGenerating:"This note is already being generated",emptyNote:"The note is empty",longNoteTruncated:"Long note: only the first {count} characters will be sent to the model",generatingNotice:"Parallel Reader: asking the LLM to read and segment the full note...",noCardsReturned:"LLM returned no cards",generationDone:"Generated {count} sections{suffix}",unanchoredSuffix:" (\u26A0 {count} anchors unmatched)",cancelled:"Generation cancelled",cancelledError:"Generation cancelled",generationFailed:"Generation failed{kind}: {error}",errorCustomProviderBaseUrlRequired:"Custom provider requires an API Base URL.",errorApiBaseUrlMissing:"API Base URL is not set. Choose a provider or enter a custom base URL in settings.",errorModelMissing:"Model is not set. Enter a model ID in settings.",errorApiKeyMissing:"API key is not set. Enter an API Key in settings{hint}.",errorApiKeyEnvHint:" or set environment variable {envVar}",errorLlmNonJson:`LLM returned non-JSON: +{excerpt}`,errorProviderRequestFailed:"{label} \u8BF7\u6C42\u5931\u8D25\uFF1A{error}",errorProviderApiStatus:"{label} API \u8FD4\u56DE HTTP {status}\uFF1A{excerpt}",noEditor:"\u627E\u4E0D\u5230\u6E90\u7B14\u8BB0\u5BF9\u5E94\u7684\u7F16\u8F91\u5668\u7A97\u53E3",settingUiLanguageName:"\u754C\u9762\u8BED\u8A00",settingUiLanguageDesc:"\u63A7\u5236\u63D2\u4EF6\u754C\u9762\u3001\u547D\u4EE4\u548C\u63D0\u793A\u6587\u6848\uFF1BAuto \u8DDF\u968F Obsidian/\u7CFB\u7EDF\u8BED\u8A00",settingBackendName:"Backend",settingBackendDesc:"\u751F\u6210 bullet \u7684\u540E\u7AEF\uFF1ACLI \u590D\u7528\u672C\u673A\u767B\u5F55\uFF1BAPI \u652F\u6301 OpenAI/Anthropic/Gemini \u53CA\u517C\u5BB9\u4EE3\u7406",settingCliPathName:"CLI \u8DEF\u5F84\uFF08\u53EF\u9009\uFF09",settingCliPathDesc:"\u7559\u7A7A\u5219\u81EA\u52A8\u63A2\u6D4B\u5E38\u89C1\u4F4D\u7F6E\uFF1BObsidian GUI \u542F\u52A8\u65F6\u4E0D\u7EE7\u627F shell PATH\uFF0C\u5FC5\u8981\u65F6\u586B\u7EDD\u5BF9\u8DEF\u5F84",settingCliPathPlaceholder:"\u4F8B\uFF1A/Users/you/bin/codex",settingCliTimeoutName:"CLI \u8D85\u65F6 (ms)",apiProviderHeader:"API Provider",settingProviderPresetName:"Provider preset",settingProviderPresetDesc:"\u53C2\u8003 OpenClaw \u7684 provider/model \u601D\u8DEF\uFF1Apreset \u53EA\u8D1F\u8D23\u534F\u8BAE\u3001base URL \u548C\u8BA4\u8BC1\u9ED8\u8BA4\u503C",settingApiFormatName:"API format",settingApiFormatDesc:"\u4E0D\u540C provider \u7684 wire protocol\uFF1BOpenAI-compatible \u4EE3\u7406\u901A\u5E38\u9009 Chat Completions",settingBaseUrlName:"Base URL",settingBaseUrlDesc:"\u586B provider \u6839\u5730\u5740\uFF0C\u4E0D\u8981\u9644\u52A0 /chat/completions\uFF1B\u7559\u7A7A\u65F6\u4F7F\u7528 preset \u9ED8\u8BA4\u503C",settingApiKeyName:"API Key",settingApiKeyDesc:"\u672C\u5730 Ollama/LM Studio \u53EF\u7559\u7A7A\uFF1B\u5176\u4ED6 provider \u901A\u5E38\u9700\u8981 key",settingApiKeyEnvName:"API Key \u73AF\u5883\u53D8\u91CF",settingApiKeyEnvDesc:"\u53EF\u9009\uFF1BObsidian GUI \u4E0D\u4E00\u5B9A\u7EE7\u627F shell \u73AF\u5883\uFF0C\u76F4\u63A5\u586B API Key \u66F4\u7A33\u5B9A",settingAuthTypeName:"\u8BA4\u8BC1\u65B9\u5F0F",settingAuthTypeDesc:"Auto \u4F7F\u7528 provider preset\uFF1B\u81EA\u5B9A\u4E49\u4EE3\u7406\u53EF\u6309\u9700\u8981\u6539\u6210 Bearer\u3001x-api-key \u6216 none",settingHeadersName:"\u989D\u5916 headers",settingHeadersDesc:"\u53EF\u9009\u3002\u652F\u6301 JSON \u5BF9\u8C61\u6216\u6BCF\u884C `Header: value`\uFF0C\u7528\u4E8E Cloudflare AI Gateway \u7B49\u4EE3\u7406",settingMaxTokensName:"\u6700\u5927\u8F93\u51FA tokens",settingModelName:"Model",settingModelDescApi:"API \u8C03\u7528\u7684\u6A21\u578B ID\uFF1B\u652F\u6301 OpenClaw \u98CE\u683C provider/model\uFF0C\u82E5 provider \u524D\u7F00\u5339\u914D\u5F53\u524D preset \u4F1A\u81EA\u52A8\u5265\u79BB",settingModelDescCli:"Claude Code \u4E0B\u4F1A\u4F20 --model\uFF1BCodex \u4E0B\u901A\u5E38\u5FFD\u7565\uFF08\u7528 Codex \u9ED8\u8BA4\u914D\u7F6E\uFF09",settingMaxInputName:"\u6700\u5927\u8F93\u5165\u5B57\u7B26\u6570",settingMaxInputDesc:"\u8D85\u8FC7\u8BE5\u957F\u5EA6\u4F1A\u622A\u65AD\u540E\u518D\u53D1\u9001\u7ED9\u6A21\u578B\uFF1B\u957F\u4E0A\u4E0B\u6587\u6A21\u578B\u53EF\u9002\u5F53\u8C03\u5927",promptHeader:"Prompt",settingPromptLanguageName:"\u8F93\u51FA\u8BED\u8A00",settingPromptLanguageDesc:"\u63A7\u5236 title/gist/bullets \u7684\u8BED\u8A00\uFF1Banchor \u59CB\u7EC8\u9010\u5B57\u590D\u5236\u539F\u6587",settingCardRangeName:"\u5361\u7247\u6570\u91CF\u8303\u56F4",settingCardRangeDesc:"\u6A21\u578B\u4F1A\u5728\u8FD9\u4E2A\u8303\u56F4\u5185\u81EA\u9002\u5E94\u5207\u6BB5",settingCustomPromptName:"\u81EA\u5B9A\u4E49 system prompt",settingCustomPromptDesc:"\u53EF\u9009\u3002\u652F\u6301\u53D8\u91CF\uFF1A{minCards}\u3001{maxCards}\u3001{languageInstruction}\u3001{schema}\u3001{example}",settingCustomPromptPlaceholder:"\u7559\u7A7A\u4F7F\u7528\u5185\u7F6E prompt",settingTestBackendName:"\u6D4B\u8BD5\u5F53\u524D\u540E\u7AEF",settingTestBackendDescApi:"\u4F1A\u53D1\u8D77\u4E00\u6B21\u6700\u5C0F LLM \u8BF7\u6C42\u9A8C\u8BC1 API \u8BBE\u7F6E",settingTestBackendDescCli:"\u8C03\u7528 ` --version` \u9A8C\u8BC1 spawn \u80FD\u627E\u5230\u4E8C\u8FDB\u5236",backendTestFailed:"\u2717 \u540E\u7AEF\u6D4B\u8BD5\u5931\u8D25\uFF1A{error}",settingExportFolderName:"\u5BFC\u51FA\u6587\u4EF6\u5939",settingExportFolderDesc:"\u5BF9\u7167\u7B14\u8BB0\u751F\u6210\u4F4D\u7F6E\uFF08\u76F8\u5BF9 Vault \u6839\uFF09",cacheHeader:"\u7F13\u5B58",settingMaxCacheName:"\u6700\u5927\u7F13\u5B58\u7BC7\u6570",settingMaxCacheDesc:"\u8D85\u8FC7\u4E0A\u9650\u540E\u6309\u6700\u8FD1\u8BBF\u95EE\u65F6\u95F4\u6DD8\u6C70\u6700\u65E7\u7684\u7B14\u8BB0\u7F13\u5B58\uFF1B\u7F13\u5B58\u4FDD\u5B58\u5728\u63D2\u4EF6\u76EE\u5F55\u7684 cache.json",cachePruned:"\u5DF2\u6DD8\u6C70 {count} \u6761\u65E7\u7F13\u5B58",cachedNotesName:"\u5DF2\u7F13\u5B58\u7B14\u8BB0\uFF1A{count} \u7BC7",cachedNotesDesc:"\u7F13\u5B58\u4EE5\u6E90\u7B14\u8BB0 SHA1 + \u751F\u6210\u914D\u7F6E\u6307\u7EB9\u4F5C\u4E3A\u5931\u6548\u952E\uFF0C\u6E90\u7B14\u8BB0\u6216\u6A21\u578B\u914D\u7F6E\u4FEE\u6539\u540E\u4F1A\u663E\u793A stale \u63D0\u793A",clearAllCacheButton:"\u6E05\u9664\u6240\u6709\u7F13\u5B58",settingStreamingName:"\u6D41\u5F0F\u8F93\u51FA",settingStreamingDesc:"\u542F\u7528\u540E\u751F\u6210\u65F6\u5B9E\u65F6\u663E\u793A LLM \u8F93\u51FA\u8FDB\u5EA6\uFF08\u4EC5 OpenAI Chat \u548C Anthropic \u683C\u5F0F\u652F\u6301\uFF09",cmdBatchGenerate:"\u6279\u91CF\u751F\u6210\u5BF9\u7167\u7B14\u8BB0\uFF08\u5F53\u524D\u6587\u4EF6\u5939\uFF09",batchSelectFolder:"\u8BF7\u8F93\u5165\u8981\u6279\u91CF\u5904\u7406\u7684\u6587\u4EF6\u5939\u8DEF\u5F84\uFF08\u7559\u7A7A\u4E3A Vault \u6839\u76EE\u5F55\uFF09",batchFolderPrompt:"\u6587\u4EF6\u5939\u8DEF\u5F84",batchAlreadyRunning:"\u6279\u91CF\u751F\u6210\u6B63\u5728\u8FD0\u884C",batchCancelRequested:"\u5DF2\u8BF7\u6C42\u53D6\u6D88\u6279\u91CF\u751F\u6210\uFF1B\u5F53\u524D\u6587\u4EF6\u7ED3\u675F\u540E\u505C\u6B62",batchNoMarkdown:"\u8BE5\u6587\u4EF6\u5939\u4E0B\u6CA1\u6709 Markdown \u6587\u4EF6",batchProgress:"\u6279\u91CF\u751F\u6210\uFF1A{current}/{total}",batchDone:"\u6279\u91CF\u751F\u6210\u5B8C\u6210\uFF1A\u5171\u5904\u7406 {processed}/{total} \u7BC7\uFF0C\u8DF3\u8FC7\u7F13\u5B58 {skipped} \u7BC7",batchCancelled:"\u6279\u91CF\u751F\u6210\u5DF2\u53D6\u6D88\uFF1A\u5DF2\u5904\u7406 {processed}/{total} \u7BC7\uFF0C\u8DF3\u8FC7\u7F13\u5B58 {skipped} \u7BC7"},en:{appTitle:"Parallel Reader",settingsTitle:"Parallel Reader Settings",emptyOpenNote:"Open a note, then run:",emptyNoCache:"This note has no cached parallel notes. Run:",commandGenerate:"Parallel Reader: Generate notes for current note",displayName:"Parallel Reader",ribbonOpen:"Open Parallel Reader pane",cmdRun:"Generate parallel notes for current note (cache first)",cmdRegen:"Regenerate parallel notes (bypass cache)",cmdOpenView:"Open Parallel Reader pane",cmdExport:"Export current parallel notes to Vault",cmdCopyMarkdown:"Copy current parallel notes as Markdown",cmdCancel:"Cancel current parallel-note generation",cmdClearCurrent:"Clear cache for current note",cmdClearAll:"Clear all caches",cmdCardPrev:"Focus previous summary card",cmdCardNext:"Focus next summary card",cmdCardJump:"Jump current summary card to source",actionCancel:"Cancel generation",actionRegenerate:"Regenerate",actionCopyAll:"Copy all Markdown",actionExport:"Export to Vault",staleBanner:"The source note or generation settings changed. This is stale cache.",loadingDefault:"Generating parallel notes...",loadingGenerating:"Parallel Reader: asking the LLM to read and segment the full note...",loadingSubtitle:"You can keep reading. Cards will refresh when generation finishes.",errorTitle:"Generation failed",emptyCard:"(not generated)",anchorMismatch:"Anchor did not match; scroll sync is unavailable",menuCopyMarkdown:"Copy Markdown",menuCopyPlain:"Copy plain text",menuCopyAnchor:"Copy anchor quote",menuJumpSource:"Jump to source",menuEditCard:"Edit this card",menuDeleteCard:"Delete this card",copiedMarkdown:"Copied Markdown",copiedPlain:"Copied plain text",copiedAnchor:"Copied anchor quote",copiedAllMarkdown:"Copied all Markdown",copyFailed:"Copy failed: {error}",actionFailed:"{label} failed: {error}",exported:"Exported \u2192 {path}",noCurrentNote:"No current note",cacheClearedFile:"Cleared cache: {name}",cacheClearedAll:"Cleared {count} cache entries",noCancelableJob:"No cancellable generation job",cancelRequested:"Cancel requested",cancelRequestedApiInFlight:"Cancel requested. The in-flight API request cannot be aborted immediately; its result will be ignored.",fileMenuGenerate:"Generate parallel notes",fileMenuRegen:"Regenerate parallel notes",fileMenuClear:"Clear parallel-note cache",noExportContent:"No parallel notes to export",noCopyContent:"No parallel notes to copy",noActiveCard:"No active summary card to jump",viewOpenFailed:"Could not open the Parallel Reader pane",confirmRegenerateEditedCards:"These parallel-reader cards were edited manually. Regenerating will overwrite those edits. Continue?",regenerateCancelled:"Regeneration cancelled",cardDeleted:"Deleted this card",cardSaved:"Saved this card",editCardTitle:"Edit summary card",editCardTitleField:"Title",editCardGistField:"Gist",editCardBulletsField:"Bullets (one per line)",editCardCancel:"Cancel",editCardSave:"Save",openNoteFirst:"Open a note first",alreadyGenerating:"This note is already being generated",emptyNote:"The note is empty",longNoteTruncated:"Long note: only the first {count} characters will be sent to the model",generatingNotice:"Parallel Reader: asking the LLM to read and segment the full note...",noCardsReturned:"LLM returned no cards",generationDone:"Generated {count} sections{suffix}",unanchoredSuffix:" (\u26A0 {count} anchors unmatched)",cancelled:"Generation cancelled",cancelledError:"Generation cancelled",generationFailed:"Generation failed{kind}: {error}",errorCustomProviderBaseUrlRequired:"Custom provider requires an API Base URL.",errorApiBaseUrlMissing:"API Base URL is not set. Choose a provider or enter a custom base URL in settings.",errorModelMissing:"Model is not set. Enter a model ID in settings.",errorApiKeyMissing:"API key is not set. Enter an API Key in settings{hint}.",errorApiKeyEnvHint:" or set environment variable {envVar}",errorLlmNonJson:`LLM returned non-JSON: {excerpt}`,errorCustomHeadersJsonParse:"Custom headers JSON parse failed: {error}",errorCustomHeadersJsonObject:"Custom headers JSON must be an object",errorCustomHeadersLineFormat:"Custom headers lines must use `Header-Name: value`",errorProviderNonJson:`{label} returned non-JSON: -{excerpt}`,errorProviderRequestFailed:"{label} request failed: {error}",errorProviderApiStatus:"{label} API returned HTTP {status}: {excerpt}",noEditor:"Could not find the source note editor",settingUiLanguageName:"UI language",settingUiLanguageDesc:"Controls plugin UI, commands, and notices. Auto follows Obsidian/system language.",settingBackendName:"Backend",settingBackendDesc:"Backend for generating bullets: CLI reuses local login; API supports OpenAI, Anthropic, Gemini, and compatible proxies.",settingCliPathName:"CLI path (optional)",settingCliPathDesc:"Leave blank to auto-detect common paths. Obsidian launched from the GUI may not inherit shell PATH.",settingCliPathPlaceholder:"Example: /Users/you/bin/codex",settingCliTimeoutName:"CLI timeout (ms)",apiProviderHeader:"API Provider",settingProviderPresetName:"Provider preset",settingProviderPresetDesc:"OpenClaw-style provider/model setup: presets define protocol, base URL, and auth defaults.",settingApiFormatName:"API format",settingApiFormatDesc:"Wire protocol for the provider. OpenAI-compatible proxies usually use Chat Completions.",settingBaseUrlName:"Base URL",settingBaseUrlDesc:"Provider root URL, without /chat/completions. Blank uses the preset default.",settingApiKeyName:"API Key",settingApiKeyDesc:"Can be blank for local Ollama/LM Studio; most hosted providers require a key.",settingApiKeyEnvName:"API key env var",settingApiKeyEnvDesc:"Optional. Obsidian GUI may not inherit shell env; direct API Key is usually more reliable.",settingAuthTypeName:"Auth type",settingAuthTypeDesc:"Auto uses provider preset. Custom proxies can use Bearer, x-api-key, or none.",settingHeadersName:"Extra headers",settingHeadersDesc:"Optional. JSON object or one `Header: value` per line, useful for Cloudflare AI Gateway.",settingMaxTokensName:"Max output tokens",settingModelName:"Model",settingModelDescApi:"Model ID for API calls. Supports OpenClaw-style provider/model; matching provider prefixes are stripped.",settingModelDescCli:"Passed as --model for Claude Code. Usually ignored by Codex, which uses its default config.",settingMaxInputName:"Max input characters",settingMaxInputDesc:"Longer notes are truncated before sending to the model. Raise this for long-context models.",promptHeader:"Prompt",settingPromptLanguageName:"Output language",settingPromptLanguageDesc:"Controls title/gist/bullets language. Anchor is always copied verbatim from source.",settingCardRangeName:"Card count range",settingCardRangeDesc:"The model adapts segmentation within this range.",settingCustomPromptName:"Custom system prompt",settingCustomPromptDesc:"Optional. Variables: {minCards}, {maxCards}, {languageInstruction}, {schema}, {example}",settingCustomPromptPlaceholder:"Leave blank to use built-in prompt",settingTestBackendName:"Test current backend",settingTestBackendDescApi:"Sends a minimal LLM request to validate API settings.",settingTestBackendDescCli:"Runs ` --version` to verify the binary can be spawned.",backendTestFailed:"\u2717 Backend test failed: {error}",settingExportFolderName:"Export folder",settingExportFolderDesc:"Parallel-note output location, relative to the Vault root.",cacheHeader:"Cache",settingMaxCacheName:"Max cached notes",settingMaxCacheDesc:"Prunes least-recently accessed note caches above this limit. Cache is stored in plugin cache.json.",cachePruned:"Pruned {count} old cache entries",cachedNotesName:"Cached notes: {count}",cachedNotesDesc:"Cache is invalidated by source SHA1 and generation settings fingerprint.",clearAllCacheButton:"Clear all caches",settingStreamingName:"Streaming output",settingStreamingDesc:"Show real-time LLM output progress during generation (OpenAI Chat and Anthropic formats only)",cmdBatchGenerate:"Batch generate parallel notes (current folder)",batchSelectFolder:"Enter the folder path to batch-process (leave blank for Vault root)",batchFolderPrompt:"Folder path",batchNoMarkdown:"No Markdown files found in that folder",batchProgress:"Batch generating: {current}/{total}",batchDone:"Batch complete: processed {total}, skipped {skipped} (cached)"}};function Ft(r){let t=r?.uiLanguage;return t==="zh"||t==="en"?t:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function w(r,t,e){let n=Ft(r),i=se[n]||se.en,a=se.en[t]||se.zh[t]||t,s=i[t]||a;return String(s).replace(/\{([a-zA-Z0-9_]+)\}/g,(o,l)=>e&&Object.hasOwn(e,l)?String(e[l]):o)}var Te=1e5,Rt=2,B=2,j=100,$={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},Ee={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},b={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Te,maxCacheEntries:j,promptLanguage:"zh",minCards:5,maxCards:15,customSystemPrompt:"",model:"claude-sonnet-4-6",exportFolder:"Reading/Articles",cliTimeoutMs:12e4,streaming:!0,streamingTimeoutMs:12e4},S={"anthropic-messages":{label:"Anthropic Messages",defaultBaseUrl:"https://api.anthropic.com/v1",defaultAuthType:"x-api-key"},"openai-chat":{label:"OpenAI Chat Completions",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer",tokenLimitField:"max_tokens"},"openai-responses":{label:"OpenAI Responses",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer"},"google-generative-ai":{label:"Google Gemini generateContent",defaultBaseUrl:"https://generativelanguage.googleapis.com/v1beta",defaultAuthType:"x-goog-api-key"}},ke={auto:"Auto",bearer:"Authorization: Bearer","x-api-key":"x-api-key","x-goog-api-key":"x-goog-api-key","api-key":"api-key",none:"None"},R={anthropic:{label:"Anthropic",format:"anthropic-messages",baseUrl:"https://api.anthropic.com/v1",authType:"x-api-key",envVar:"ANTHROPIC_API_KEY",model:"claude-sonnet-4-6"},openai:{label:"OpenAI",format:"openai-chat",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",tokenLimitField:"max_completion_tokens",model:"gpt-5.1"},"openai-responses":{label:"OpenAI Responses",format:"openai-responses",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",modelPrefix:"openai",model:"gpt-5.1"},google:{label:"Google Gemini",format:"google-generative-ai",baseUrl:"https://generativelanguage.googleapis.com/v1beta",authType:"x-goog-api-key",envVar:"GEMINI_API_KEY",model:"gemini-3-pro-preview"},openrouter:{label:"OpenRouter",format:"openai-chat",baseUrl:"https://openrouter.ai/api/v1",authType:"bearer",envVar:"OPENROUTER_API_KEY",model:""},groq:{label:"Groq",format:"openai-chat",baseUrl:"https://api.groq.com/openai/v1",authType:"bearer",envVar:"GROQ_API_KEY",model:""},deepseek:{label:"DeepSeek",format:"openai-chat",baseUrl:"https://api.deepseek.com",authType:"bearer",envVar:"DEEPSEEK_API_KEY",model:"deepseek-chat"},moonshot:{label:"Moonshot / Kimi",format:"openai-chat",baseUrl:"https://api.moonshot.ai/v1",authType:"bearer",envVar:"MOONSHOT_API_KEY",model:"kimi-k2.5"},qianfan:{label:"Baidu Qianfan",format:"openai-chat",baseUrl:"https://qianfan.baidubce.com/v2",authType:"bearer",envVar:"QIANFAN_API_KEY",model:"deepseek-v3.2"},minimax:{label:"MiniMax (Anthropic-compatible)",format:"anthropic-messages",baseUrl:"https://api.minimax.io/anthropic",authType:"bearer",envVar:"MINIMAX_API_KEY",model:"MiniMax-M2.1"},xai:{label:"xAI",format:"openai-chat",baseUrl:"https://api.x.ai/v1",authType:"bearer",envVar:"XAI_API_KEY",model:""},mistral:{label:"Mistral",format:"openai-chat",baseUrl:"https://api.mistral.ai/v1",authType:"bearer",envVar:"MISTRAL_API_KEY",model:""},cerebras:{label:"Cerebras",format:"openai-chat",baseUrl:"https://api.cerebras.ai/v1",authType:"bearer",envVar:"CEREBRAS_API_KEY",model:""},zai:{label:"Z.AI / GLM",format:"openai-chat",baseUrl:"https://api.z.ai/api/paas/v4",authType:"bearer",envVar:"ZAI_API_KEY",model:""},ollama:{label:"Ollama (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:11434/v1",authType:"none",envVar:"",model:""},lmstudio:{label:"LM Studio (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:1234/v1",authType:"none",envVar:"",model:""},"custom-openai":{label:"Custom OpenAI-compatible",format:"openai-chat",baseUrl:"",authType:"bearer",envVar:"",model:""},"custom-anthropic":{label:"Custom Anthropic-compatible",format:"anthropic-messages",baseUrl:"",authType:"x-api-key",envVar:"",model:""}};function V(r){return it.default.createHash("sha1").update(r,"utf8").digest("hex")}function Se(r){return Array.isArray(r)?"["+r.map(Se).join(",")+"]":r&&typeof r=="object"?"{"+Object.keys(r).sort().map(t=>JSON.stringify(t)+":"+Se(r[t])).join(",")+"}":JSON.stringify(r)}function Fe(r){return r==="api"||r==="anthropic-api"}function x(r){return R[r.apiProvider]||R.anthropic}function P(r){let t=x(r),e=(r.apiFormat||t.format||"").trim();return S[e]?e:t.format}function F(r){let t=P(r),e=x(r),n=(r.apiBaseUrl||"").trim();if(n)return n.replace(/\/+$/,"");if((r.apiProvider||"").startsWith("custom-"))throw new Error(w(r,"errorCustomProviderBaseUrlRequired"));let i=(e.baseUrl||S[t].defaultBaseUrl||"").trim();if(!i)throw new Error(w(r,"errorApiBaseUrlMissing"));return i.replace(/\/+$/,"")}function Re(r){let t=(r.apiAuthType||"auto").trim();if(t&&t!=="auto")return t;let e=x(r),n=P(r);return e.authType||S[n].defaultAuthType||"bearer"}function at(r){let t=(r.apiKey||"").trim();if(t)return t;let e=(r.apiKeyEnvVar||x(r).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function I(r){let t=(r.model||"").trim();if(!t)throw new Error(w(r,"errorModelMissing"));let e=x(r),n=[r.apiProvider,e.modelPrefix].map(a=>(a||"").trim()).filter(Boolean),i=t.toLowerCase();for(let a of n){let s=a.toLowerCase();if(i.startsWith(s+"/"))return t.slice(a.length+1).trim()}return t}function Me(r,t){let e=x(r),n=R[t]||R.anthropic,i=(r.model||"").trim(),a=!i||i===b.model||!!e.model&&i===e.model;r.apiProvider=t,r.apiFormat=n.format,r.apiBaseUrl=n.baseUrl,r.apiAuthType=n.authType||"auto",r.apiKeyEnvVar=n.envVar||"",a&&(r.model=n.model||"")}function Ne(r){Ee[r.uiLanguage]||(r.uiLanguage=b.uiLanguage),(!r.apiProvider||!R[r.apiProvider])&&(r.apiProvider="anthropic");let t=x(r);(!r.apiFormat||!S[r.apiFormat])&&(r.apiFormat=t.format),(!r.apiAuthType||!ke[r.apiAuthType])&&(r.apiAuthType="auto"),r.backend==="anthropic-api"&&(r.apiProvider=r.apiProvider||"anthropic",r.apiFormat=r.apiFormat||"anthropic-messages",r.apiBaseUrl=r.apiBaseUrl||R.anthropic.baseUrl,r.apiAuthType=r.apiAuthType||"x-api-key",r.apiKeyEnvVar=r.apiKeyEnvVar||"ANTHROPIC_API_KEY");let e=Number(r.apiMaxTokens);(!Number.isFinite(e)||e<=0)&&(r.apiMaxTokens=b.apiMaxTokens);let n=Number(r.maxDocChars);return(!Number.isFinite(n)||n<1e3)&&(r.maxDocChars=b.maxDocChars),r.maxCacheEntries=st(r.maxCacheEntries),$[r.promptLanguage]||(r.promptLanguage=b.promptLanguage),r.minCards=rt(r.minCards,b.minCards),r.maxCards=rt(r.maxCards,b.maxCards),r.maxCardsnt(s)-nt(l)||a.localeCompare(o)).slice(0,n.length-e).map(([a])=>a);for(let a of i)delete r[a];return i}function K(r){let t=Ne(Object.assign({},b,r||{})),e=Fe(t.backend),n=x(t),i=P(t),a=e?(t.apiBaseUrl||n.baseUrl||S[i]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return V(Se({cacheSchemaVersion:B,promptVersion:Rt,maxDocChars:Number(t.maxDocChars)||b.maxDocChars,promptLanguage:t.promptLanguage,minCards:t.minCards,maxCards:t.maxCards,customSystemPromptHash:V(t.customSystemPrompt||""),backend:t.backend,model:t.model,apiProvider:e?t.apiProvider:"",apiFormat:e?i:"",apiBaseUrl:a,apiAuthType:e?Re(t):"",apiHeadersHash:e?V(t.apiHeaders||""):"",apiMaxTokens:e?Number(t.apiMaxTokens)||b.apiMaxTokens:0,structuredOutputVersion:1}))}function U(r,t,e){return!!r&&r.schemaVersion===B&&r.contentHash===V(t)&&r.settingsHash===K(e)}function Ie(r){return(r||"").trim().split("/").map(t=>t.trim()).filter(t=>t&&t!=="."&&t!=="..").join("/")}function Mt(r,t){return t?r.parent?.path===t:!r.path.includes("/")}function De(r,t){let e=Ie(t);return r.filter(n=>Mt(n,e))}function Le(r,t){return{current:r+1,total:t}}function Oe(r){return{total:r,skipped:0}}function _e(r){return{...r,skipped:r.skipped+1}}function Ve(r,t,e){return!!r&&U(r,t,e)}function le(r,t){return r?{...r,lastAccessedAt:t||new Date().toISOString()}:null}function ce(r){return JSON.stringify({version:1,entries:r||{}})}function Be(r,t){return!!t&&!!r&&typeof r.updatedAt=="string"&&r.updatedAt.trim().length>0}var q=class{constructor(t,e,n,i){this.adapter=t;this.configDir=e;this.pluginId=n;this.getSettings=i;this.cache={};this._timer=null;this._dirty=!1}filePath(){return`${this.configDir}/plugins/${this.pluginId}/cache.json`}async ensureDir(){let t=`${this.configDir}/plugins/${this.pluginId}`;try{if(typeof this.adapter.exists=="function"&&await this.adapter.exists(t))return;await this.adapter.mkdir(t)}catch{}}async readFile(){try{let t=await this.adapter.read(this.filePath()),e=JSON.parse(t);if(e&&typeof e=="object"&&e.entries&&typeof e.entries=="object")return e.entries}catch(t){let e=t instanceof Error?t.message:String(t);/not found|does not exist|ENOENT/i.test(e)||console.warn("[parallel-reader] failed to read cache.json",t)}return{}}async writeFile(){await this.ensureDir(),await this.adapter.write(this.filePath(),ce(this.cache))}async load(){this.cache=await this.readFile(),this.prune().length>0&&await this.writeFile()}prune(){let t=this.getSettings();return oe(this.cache,t?.maxCacheEntries||j)}async pruneIfNeeded(){let t=this.prune();return t.length>0&&await this.save(),t}async save(){this._timer&&(clearTimeout(this._timer),this._timer=null),this.prune(),await this.writeFile(),this._dirty=!1}scheduleSave(t=5e3){this._dirty=!0,!this._timer&&(this._timer=setTimeout(()=>{this._timer=null,this._dirty&&this.save().catch(e=>console.error("[parallel-reader] failed to save cache",e))},t))}async flush(){this._timer&&(clearTimeout(this._timer),this._timer=null),this._dirty&&await this.save()}get(t){return this.cache[t]||null}async touch(t){let e=le(this.cache[t]||null);return e?(this.cache[t]=e,this.scheduleSave(),e):null}async move(t,e){if(typeof t!="string"||typeof e!="string"||!t.trim()||!e.trim())return!1;if(t===e)return Object.hasOwn(this.cache,t);let n=this.cache[t];if(!n||Object.hasOwn(this.cache,e))return!1;let i={...this.cache,[e]:n};return delete i[t],this.cache=i,await this.save(),!0}async put(t,e,n,i){let a=new Date().toISOString();this.cache[t]={schemaVersion:B,contentHash:V(e),settingsHash:K(i||this.getSettings()),cards:n,generatedAt:a,lastAccessedAt:a},await this.save()}async replaceCards(t,e){let n=this.cache[t];if(!n)return!1;let i=new Date().toISOString();return this.cache[t]={...n,cards:(e||[]).map(a=>({title:a.title,anchor:a.anchor,gist:a.gist,bullets:a.bullets||[]})),updatedAt:i,lastAccessedAt:i},await this.save(),!0}async delete(t){this.cache[t]&&(delete this.cache[t],await this.save())}async clear(){this.cache={},await this.save()}};function ue(r,t){let e=Array.isArray(r)?r.slice():[];return!Number.isInteger(t)||t<0||t>=e.length||e.splice(t,1),e}function de(r,t,e){if(!Number.isInteger(r)||!Number.isInteger(t)||t<=0||!Number.isInteger(e)||e<0||r<0||r>=t)return e;let n=t-1;return n<=0?-1:r=n.length||(n[t]=Object.assign({},n[t],e||{})),n}var ut=require("child_process"),dt=ae(require("fs")),me=ae(require("os")),v=ae(require("path"));var G=class extends Error{constructor(t){super("\u8BE5\u7B14\u8BB0\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0"),this.name="GenerationJobAlreadyRunningError",this.code="already-running",this.key=t}},T=class extends Error{constructor(t){super("\u751F\u6210\u5DF2\u53D6\u6D88"),this.name="GenerationJobCancelledError",this.code="cancelled",this.key=t}},je=class{constructor(t){this.key=t,this.phase="queued",this.cancelled=!1,this.startedAt=new Date().toISOString(),this.updatedAt=this.startedAt,this._cancelHandlers=[]}setPhase(t){this.phase=t,this.updatedAt=new Date().toISOString()}onCancel(t){if(typeof t=="function"){if(this.cancelled){t();return}this._cancelHandlers.push(t)}}cancel(){if(this.cancelled)return!1;this.cancelled=!0,this.setPhase("cancelled");for(let t of this._cancelHandlers.splice(0))try{t()}catch{}return!0}throwIfCancelled(){if(this.cancelled)throw new T(this.key)}},z=class{constructor(){this.jobs=new Map}get(t){return this.jobs.get(t)||null}isRunning(t){return this.jobs.has(t)}async start(t,e){if(this.jobs.has(t))throw new G(t);let n=new je(t);this.jobs.set(t,n);try{n.setPhase("running");let i=await e(n);return n.throwIfCancelled(),i}catch(i){throw n.cancelled&&!(i instanceof T)?new T(t):i}finally{this.jobs.delete(t)}}cancel(t){let e=this.jobs.get(t);return e?e.cancel():!1}};function Ue(r){if(r instanceof T)return"cancelled";let t=r;if(t?.code==="cancelled")return"cancelled";let e=String(t?.message||r);return/api key|unauthorized|401|403|认证|权限/i.test(e)?"auth":/timeout|超时|timed out/i.test(e)?"timeout":/429|rate limit|too many requests/i.test(e)?"rate-limit":/非 JSON|json_schema|schema|structured/i.test(e)?"schema":/model 未设置|base url|配置|config/i.test(e)?"config":"unknown"}var he="record_parallel_reader_cards";function Nt(r){let t=[],e=-1,n=0,i=!1,a=!1;for(let s=0;s0&&n--,n===0&&e>=0&&(t.push(r.slice(e,s+1)),e=-1))}return t}function Ge(r){let t=(r||"").trim();if(!t)return t;try{return JSON.parse(t),t}catch{}let e=t.match(/```(?:json)?\s*([\s\S]*?)```/);if(e){let i=e[1].trim();try{return JSON.parse(i),i}catch{}}let n=Nt(t);n.sort((i,a)=>a.length-i.length);for(let i of n)try{return JSON.parse(i),i}catch{}return t}function E(r,t){let e=Ge(r),n;try{n=JSON.parse(e)}catch{throw new Error(w(t||null,"errorLlmNonJson",{excerpt:(r||"").slice(0,500)}))}return W(n)}function W(r){return(r&&Array.isArray(r.cards)?r.cards:[]).filter(e=>!!e&&typeof e=="object").map(e=>({title:typeof e.title=="string"?e.title:"(\u65E0\u6807\u9898)",anchor:typeof e.anchor=="string"?e.anchor:"",gist:typeof e.gist=="string"?e.gist:"",bullets:Array.isArray(e.bullets)?e.bullets.filter(n=>typeof n=="string"):[]}))}function Y(r){let t={type:"object",properties:{title:{type:"string"},anchor:{type:"string"},gist:{type:"string"},bullets:{type:"array",items:{type:"string"}}},required:["title","anchor","gist","bullets"]},e={type:"object",properties:{cards:{type:"array",items:t}},required:["cards"]};return r&&(t.additionalProperties=!1,e.additionalProperties=!1),e}function ot(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:Y(!0)}}}function lt(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:Y(!0)}}}function ct(){return{name:he,description:"Return the generated Parallel Reader summary cards.",input_schema:Y(!0)}}function D(r,t){if(t?.trim())return t.trim();let e=me.default.homedir(),n=process.platform==="win32",i=n?[".cmd",".exe",""]:[""],a=n?[v.default.join(e,"AppData","Roaming","npm"),v.default.join(e,"AppData","Local","Programs","claude-code"),v.default.join(e,"AppData","Local","Programs","codex"),v.default.join(e,".claude","local"),v.default.join(e,".codex","bin"),v.default.join(e,".bun","bin"),v.default.join(e,"scoop","shims"),"C:\\Program Files\\nodejs"]:[v.default.join(e,"bin"),v.default.join(e,".local","bin"),v.default.join(e,".claude","local"),v.default.join(e,".codex","bin"),v.default.join(e,".bun","bin"),v.default.join(e,".npm-global","bin"),v.default.join(e,".cargo","bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let s of a)for(let o of i){let l=v.default.join(s,r+o);try{if(dt.default.existsSync(l))return l}catch{}}return r}function X(r,t,e,n,i){return new Promise((a,s)=>{let o,l=!1,c=null,u=C=>{l||(l=!0,c&&clearTimeout(c),s(C))},m=C=>{l||(l=!0,c&&clearTimeout(c),a(C))};try{o=(0,ut.spawn)(r,t,{stdio:["pipe","pipe","pipe"],env:{...process.env,PATH:[process.env.PATH||"","/usr/local/bin","/opt/homebrew/bin",v.default.join(me.default.homedir(),".local/bin"),v.default.join(me.default.homedir(),".claude/local")].filter(Boolean).join(":")}})}catch(C){return s(new Error(`Failed to start ${r}: ${C.message}`))}let g="",d="";if(c=setTimeout(()=>{try{o.kill("SIGKILL")}catch{}u(new Error(`CLI timed out (${n}ms)`))},n),i&&i.onCancel(()=>{try{o.kill("SIGKILL")}catch{}u(new T(i.key))}),!o.stdout||!o.stderr||!o.stdin){u(new Error("CLI process streams are unavailable"));return}let f=o.stdout,y=o.stderr,_=o.stdin;if(f.on("data",C=>{g+=C.toString("utf8")}),y.on("data",C=>{d+=C.toString("utf8")}),o.on("error",C=>{u(new Error(`CLI startup error: ${C.message}. Try setting an absolute CLI path.`))}),o.on("close",C=>{if(!l){if(C!==0)return u(new Error(`CLI exited with code ${C} +{excerpt}`,errorProviderRequestFailed:"{label} request failed: {error}",errorProviderApiStatus:"{label} API returned HTTP {status}: {excerpt}",noEditor:"Could not find the source note editor",settingUiLanguageName:"UI language",settingUiLanguageDesc:"Controls plugin UI, commands, and notices. Auto follows Obsidian/system language.",settingBackendName:"Backend",settingBackendDesc:"Backend for generating bullets: CLI reuses local login; API supports OpenAI, Anthropic, Gemini, and compatible proxies.",settingCliPathName:"CLI path (optional)",settingCliPathDesc:"Leave blank to auto-detect common paths. Obsidian launched from the GUI may not inherit shell PATH.",settingCliPathPlaceholder:"Example: /Users/you/bin/codex",settingCliTimeoutName:"CLI timeout (ms)",apiProviderHeader:"API Provider",settingProviderPresetName:"Provider preset",settingProviderPresetDesc:"OpenClaw-style provider/model setup: presets define protocol, base URL, and auth defaults.",settingApiFormatName:"API format",settingApiFormatDesc:"Wire protocol for the provider. OpenAI-compatible proxies usually use Chat Completions.",settingBaseUrlName:"Base URL",settingBaseUrlDesc:"Provider root URL, without /chat/completions. Blank uses the preset default.",settingApiKeyName:"API Key",settingApiKeyDesc:"Can be blank for local Ollama/LM Studio; most hosted providers require a key.",settingApiKeyEnvName:"API key env var",settingApiKeyEnvDesc:"Optional. Obsidian GUI may not inherit shell env; direct API Key is usually more reliable.",settingAuthTypeName:"Auth type",settingAuthTypeDesc:"Auto uses provider preset. Custom proxies can use Bearer, x-api-key, or none.",settingHeadersName:"Extra headers",settingHeadersDesc:"Optional. JSON object or one `Header: value` per line, useful for Cloudflare AI Gateway.",settingMaxTokensName:"Max output tokens",settingModelName:"Model",settingModelDescApi:"Model ID for API calls. Supports OpenClaw-style provider/model; matching provider prefixes are stripped.",settingModelDescCli:"Passed as --model for Claude Code. Usually ignored by Codex, which uses its default config.",settingMaxInputName:"Max input characters",settingMaxInputDesc:"Longer notes are truncated before sending to the model. Raise this for long-context models.",promptHeader:"Prompt",settingPromptLanguageName:"Output language",settingPromptLanguageDesc:"Controls title/gist/bullets language. Anchor is always copied verbatim from source.",settingCardRangeName:"Card count range",settingCardRangeDesc:"The model adapts segmentation within this range.",settingCustomPromptName:"Custom system prompt",settingCustomPromptDesc:"Optional. Variables: {minCards}, {maxCards}, {languageInstruction}, {schema}, {example}",settingCustomPromptPlaceholder:"Leave blank to use built-in prompt",settingTestBackendName:"Test current backend",settingTestBackendDescApi:"Sends a minimal LLM request to validate API settings.",settingTestBackendDescCli:"Runs ` --version` to verify the binary can be spawned.",backendTestFailed:"\u2717 Backend test failed: {error}",settingExportFolderName:"Export folder",settingExportFolderDesc:"Parallel-note output location, relative to the Vault root.",cacheHeader:"Cache",settingMaxCacheName:"Max cached notes",settingMaxCacheDesc:"Prunes least-recently accessed note caches above this limit. Cache is stored in plugin cache.json.",cachePruned:"Pruned {count} old cache entries",cachedNotesName:"Cached notes: {count}",cachedNotesDesc:"Cache is invalidated by source SHA1 and generation settings fingerprint.",clearAllCacheButton:"Clear all caches",settingStreamingName:"Streaming output",settingStreamingDesc:"Show real-time LLM output progress during generation (OpenAI Chat and Anthropic formats only)",cmdBatchGenerate:"Batch generate parallel notes (current folder)",batchSelectFolder:"Enter the folder path to batch-process (leave blank for Vault root)",batchFolderPrompt:"Folder path",batchAlreadyRunning:"Batch generation is already running",batchCancelRequested:"Batch cancellation requested; it will stop after the current note",batchNoMarkdown:"No Markdown files found in that folder",batchProgress:"Batch generating: {current}/{total}",batchDone:"Batch complete: processed {processed}/{total}, skipped {skipped} (cached)",batchCancelled:"Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached)"}};function It(r){let t=r?.uiLanguage;return t==="zh"||t==="en"?t:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function w(r,t,e){let n=It(r),i=se[n]||se.en,a=se.en[t]||se.zh[t]||t,s=i[t]||a;return String(s).replace(/\{([a-zA-Z0-9_]+)\}/g,(o,c)=>e&&Object.hasOwn(e,c)?String(e[c]):o)}var ke=1e5,Dt=2,V=2,j=100,$={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},Ee={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},b={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:ke,maxCacheEntries:j,promptLanguage:"zh",minCards:5,maxCards:15,customSystemPrompt:"",model:"claude-sonnet-4-6",exportFolder:"Reading/Articles",cliTimeoutMs:12e4,streaming:!0,streamingTimeoutMs:12e4},S={"anthropic-messages":{label:"Anthropic Messages",defaultBaseUrl:"https://api.anthropic.com/v1",defaultAuthType:"x-api-key"},"openai-chat":{label:"OpenAI Chat Completions",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer",tokenLimitField:"max_tokens"},"openai-responses":{label:"OpenAI Responses",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer"},"google-generative-ai":{label:"Google Gemini generateContent",defaultBaseUrl:"https://generativelanguage.googleapis.com/v1beta",defaultAuthType:"x-goog-api-key"}},Fe={auto:"Auto",bearer:"Authorization: Bearer","x-api-key":"x-api-key","x-goog-api-key":"x-goog-api-key","api-key":"api-key",none:"None"},R={anthropic:{label:"Anthropic",format:"anthropic-messages",baseUrl:"https://api.anthropic.com/v1",authType:"x-api-key",envVar:"ANTHROPIC_API_KEY",model:"claude-sonnet-4-6"},openai:{label:"OpenAI",format:"openai-chat",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",tokenLimitField:"max_completion_tokens",model:"gpt-5.1"},"openai-responses":{label:"OpenAI Responses",format:"openai-responses",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",modelPrefix:"openai",model:"gpt-5.1"},google:{label:"Google Gemini",format:"google-generative-ai",baseUrl:"https://generativelanguage.googleapis.com/v1beta",authType:"x-goog-api-key",envVar:"GEMINI_API_KEY",model:"gemini-3-pro-preview"},openrouter:{label:"OpenRouter",format:"openai-chat",baseUrl:"https://openrouter.ai/api/v1",authType:"bearer",envVar:"OPENROUTER_API_KEY",model:""},groq:{label:"Groq",format:"openai-chat",baseUrl:"https://api.groq.com/openai/v1",authType:"bearer",envVar:"GROQ_API_KEY",model:""},deepseek:{label:"DeepSeek",format:"openai-chat",baseUrl:"https://api.deepseek.com",authType:"bearer",envVar:"DEEPSEEK_API_KEY",model:"deepseek-chat"},moonshot:{label:"Moonshot / Kimi",format:"openai-chat",baseUrl:"https://api.moonshot.ai/v1",authType:"bearer",envVar:"MOONSHOT_API_KEY",model:"kimi-k2.5"},qianfan:{label:"Baidu Qianfan",format:"openai-chat",baseUrl:"https://qianfan.baidubce.com/v2",authType:"bearer",envVar:"QIANFAN_API_KEY",model:"deepseek-v3.2"},minimax:{label:"MiniMax (Anthropic-compatible)",format:"anthropic-messages",baseUrl:"https://api.minimax.io/anthropic",authType:"bearer",envVar:"MINIMAX_API_KEY",model:"MiniMax-M2.1"},xai:{label:"xAI",format:"openai-chat",baseUrl:"https://api.x.ai/v1",authType:"bearer",envVar:"XAI_API_KEY",model:""},mistral:{label:"Mistral",format:"openai-chat",baseUrl:"https://api.mistral.ai/v1",authType:"bearer",envVar:"MISTRAL_API_KEY",model:""},cerebras:{label:"Cerebras",format:"openai-chat",baseUrl:"https://api.cerebras.ai/v1",authType:"bearer",envVar:"CEREBRAS_API_KEY",model:""},zai:{label:"Z.AI / GLM",format:"openai-chat",baseUrl:"https://api.z.ai/api/paas/v4",authType:"bearer",envVar:"ZAI_API_KEY",model:""},ollama:{label:"Ollama (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:11434/v1",authType:"none",envVar:"",model:""},lmstudio:{label:"LM Studio (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:1234/v1",authType:"none",envVar:"",model:""},"custom-openai":{label:"Custom OpenAI-compatible",format:"openai-chat",baseUrl:"",authType:"bearer",envVar:"",model:""},"custom-anthropic":{label:"Custom Anthropic-compatible",format:"anthropic-messages",baseUrl:"",authType:"x-api-key",envVar:"",model:""}};function _(r){return ct.default.createHash("sha1").update(r,"utf8").digest("hex")}function Te(r){return Array.isArray(r)?"["+r.map(Te).join(",")+"]":r&&typeof r=="object"?"{"+Object.keys(r).sort().map(t=>JSON.stringify(t)+":"+Te(r[t])).join(",")+"}":JSON.stringify(r)}function Re(r){return r==="api"||r==="anthropic-api"}function x(r){return R[r.apiProvider]||R.anthropic}function P(r){let t=x(r),e=(r.apiFormat||t.format||"").trim();return S[e]?e:t.format}function F(r){let t=P(r),e=x(r),n=(r.apiBaseUrl||"").trim();if(n)return n.replace(/\/+$/,"");if((r.apiProvider||"").startsWith("custom-"))throw new Error(w(r,"errorCustomProviderBaseUrlRequired"));let i=(e.baseUrl||S[t].defaultBaseUrl||"").trim();if(!i)throw new Error(w(r,"errorApiBaseUrlMissing"));return i.replace(/\/+$/,"")}function Me(r){let t=(r.apiAuthType||"auto").trim();if(t&&t!=="auto")return t;let e=x(r),n=P(r);return e.authType||S[n].defaultAuthType||"bearer"}function lt(r){let t=(r.apiKey||"").trim();if(t)return t;let e=(r.apiKeyEnvVar||x(r).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function I(r){let t=(r.model||"").trim();if(!t)throw new Error(w(r,"errorModelMissing"));let e=x(r),n=[r.apiProvider,e.modelPrefix].map(a=>(a||"").trim()).filter(Boolean),i=t.toLowerCase();for(let a of n){let s=a.toLowerCase();if(i.startsWith(s+"/"))return t.slice(a.length+1).trim()}return t}function Ne(r,t){let e=x(r),n=R[t]||R.anthropic,i=(r.model||"").trim(),a=!i||i===b.model||!!e.model&&i===e.model;r.apiProvider=t,r.apiFormat=n.format,r.apiBaseUrl=n.baseUrl,r.apiAuthType=n.authType||"auto",r.apiKeyEnvVar=n.envVar||"",a&&(r.model=n.model||"")}function Ie(r){Ee[r.uiLanguage]||(r.uiLanguage=b.uiLanguage),(!r.apiProvider||!R[r.apiProvider])&&(r.apiProvider="anthropic");let t=x(r);(!r.apiFormat||!S[r.apiFormat])&&(r.apiFormat=t.format),(!r.apiAuthType||!Fe[r.apiAuthType])&&(r.apiAuthType="auto"),r.backend==="anthropic-api"&&(r.apiProvider=r.apiProvider||"anthropic",r.apiFormat=r.apiFormat||"anthropic-messages",r.apiBaseUrl=r.apiBaseUrl||R.anthropic.baseUrl,r.apiAuthType=r.apiAuthType||"x-api-key",r.apiKeyEnvVar=r.apiKeyEnvVar||"ANTHROPIC_API_KEY");let e=Number(r.apiMaxTokens);(!Number.isFinite(e)||e<=0)&&(r.apiMaxTokens=b.apiMaxTokens);let n=Number(r.maxDocChars);return(!Number.isFinite(n)||n<1e3)&&(r.maxDocChars=b.maxDocChars),r.maxCacheEntries=ut(r.maxCacheEntries),$[r.promptLanguage]||(r.promptLanguage=b.promptLanguage),r.minCards=st(r.minCards,b.minCards),r.maxCards=st(r.maxCards,b.maxCards),r.maxCardsot(s)-ot(c)||a.localeCompare(o)).slice(0,n.length-e).map(([a])=>a);for(let a of i)delete r[a];return i}function K(r){let t=Ie(Object.assign({},b,r||{})),e=Re(t.backend),n=x(t),i=P(t),a=e?(t.apiBaseUrl||n.baseUrl||S[i]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return _(Te({cacheSchemaVersion:V,promptVersion:Dt,maxDocChars:Number(t.maxDocChars)||b.maxDocChars,promptLanguage:t.promptLanguage,minCards:t.minCards,maxCards:t.maxCards,customSystemPromptHash:_(t.customSystemPrompt||""),backend:t.backend,model:t.model,apiProvider:e?t.apiProvider:"",apiFormat:e?i:"",apiBaseUrl:a,apiAuthType:e?Me(t):"",apiHeadersHash:e?_(t.apiHeaders||""):"",apiMaxTokens:e?Number(t.apiMaxTokens)||b.apiMaxTokens:0,structuredOutputVersion:1}))}function U(r,t,e){return!!r&&r.schemaVersion===V&&r.contentHash===_(t)&&r.settingsHash===K(e)}function De(r){return(r||"").trim().split("/").map(t=>t.trim()).filter(t=>t&&t!=="."&&t!=="..").join("/")}function Lt(r,t){return t?r.parent?.path===t:!r.path.includes("/")}function Le(r,t){let e=De(t);return r.filter(n=>Lt(n,e))}function Oe(r,t){return{current:r+1,total:t}}function Be(r){return{total:r,processed:0,skipped:0}}function _e(r){return{...r,processed:r.processed+1}}function Ve(r){return{...r,processed:r.processed+1,skipped:r.skipped+1}}function je(r,t,e){return!!r&&U(r,t,e)}function Ue(){return{cancelled:!1,currentPath:""}}function Ge(r,t){return r.currentPath=t,r}function ce(r){return!r||r.cancelled?!1:(r.cancelled=!0,!0)}function le(r,t){return r?{...r,lastAccessedAt:t||new Date().toISOString()}:null}function ue(r){return JSON.stringify({version:1,entries:r||{}})}function He(r,t){return!!t&&!!r&&typeof r.updatedAt=="string"&&r.updatedAt.trim().length>0}var q=class{constructor(t,e,n,i){this.adapter=t;this.configDir=e;this.pluginId=n;this.getSettings=i;this.cache={};this._timer=null;this._dirty=!1}filePath(){return`${this.configDir}/plugins/${this.pluginId}/cache.json`}async ensureDir(){let t=`${this.configDir}/plugins/${this.pluginId}`;try{if(typeof this.adapter.exists=="function"&&await this.adapter.exists(t))return;await this.adapter.mkdir(t)}catch{}}async readFile(){try{let t=await this.adapter.read(this.filePath()),e=JSON.parse(t);if(e&&typeof e=="object"&&e.entries&&typeof e.entries=="object")return e.entries}catch(t){let e=t instanceof Error?t.message:String(t);/not found|does not exist|ENOENT/i.test(e)||console.warn("[parallel-reader] failed to read cache.json",t)}return{}}async writeFile(){await this.ensureDir(),await this.adapter.write(this.filePath(),ue(this.cache))}async load(){this.cache=await this.readFile(),this.prune().length>0&&await this.writeFile()}prune(){let t=this.getSettings();return oe(this.cache,t?.maxCacheEntries||j)}async pruneIfNeeded(){let t=this.prune();return t.length>0&&await this.save(),t}async save(){this._timer&&(clearTimeout(this._timer),this._timer=null),this.prune(),await this.writeFile(),this._dirty=!1}scheduleSave(t=5e3){this._dirty=!0,!this._timer&&(this._timer=setTimeout(()=>{this._timer=null,this._dirty&&this.save().catch(e=>console.error("[parallel-reader] failed to save cache",e))},t))}async flush(){this._timer&&(clearTimeout(this._timer),this._timer=null),this._dirty&&await this.save()}get(t){return this.cache[t]||null}async touch(t){let e=le(this.cache[t]||null);return e?(this.cache[t]=e,this.scheduleSave(),e):null}async move(t,e){if(typeof t!="string"||typeof e!="string"||!t.trim()||!e.trim())return!1;if(t===e)return Object.hasOwn(this.cache,t);let n=this.cache[t];if(!n||Object.hasOwn(this.cache,e))return!1;let i={...this.cache,[e]:n};return delete i[t],this.cache=i,await this.save(),!0}async put(t,e,n,i){let a=new Date().toISOString();this.cache[t]={schemaVersion:V,contentHash:_(e),settingsHash:K(i||this.getSettings()),cards:n,generatedAt:a,lastAccessedAt:a},await this.save()}async replaceCards(t,e){let n=this.cache[t];if(!n)return!1;let i=new Date().toISOString();return this.cache[t]={...n,cards:(e||[]).map(a=>({title:a.title,anchor:a.anchor,gist:a.gist,bullets:a.bullets||[]})),updatedAt:i,lastAccessedAt:i},await this.save(),!0}async delete(t){this.cache[t]&&(delete this.cache[t],await this.save())}async clear(){this.cache={},await this.save()}};function de(r,t){let e=Array.isArray(r)?r.slice():[];return!Number.isInteger(t)||t<0||t>=e.length||e.splice(t,1),e}function pe(r,t,e){if(!Number.isInteger(r)||!Number.isInteger(t)||t<=0||!Number.isInteger(e)||e<0||r<0||r>=t)return e;let n=t-1;return n<=0?-1:r=n.length||(n[t]=Object.assign({},n[t],e||{})),n}var mt=require("child_process"),gt=ae(require("fs")),ge=ae(require("os")),v=ae(require("path"));var G=class extends Error{constructor(t){super("\u8BE5\u7B14\u8BB0\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0"),this.name="GenerationJobAlreadyRunningError",this.code="already-running",this.key=t}},T=class extends Error{constructor(t){super("\u751F\u6210\u5DF2\u53D6\u6D88"),this.name="GenerationJobCancelledError",this.code="cancelled",this.key=t}},Je=class{constructor(t){this.key=t,this.phase="queued",this.cancelled=!1,this.startedAt=new Date().toISOString(),this.updatedAt=this.startedAt,this._cancelHandlers=[]}setPhase(t){this.phase=t,this.updatedAt=new Date().toISOString()}onCancel(t){if(typeof t=="function"){if(this.cancelled){t();return}this._cancelHandlers.push(t)}}cancel(){if(this.cancelled)return!1;this.cancelled=!0,this.setPhase("cancelled");for(let t of this._cancelHandlers.splice(0))try{t()}catch{}return!0}throwIfCancelled(){if(this.cancelled)throw new T(this.key)}},z=class{constructor(){this.jobs=new Map}get(t){return this.jobs.get(t)||null}isRunning(t){return this.jobs.has(t)}async start(t,e){if(this.jobs.has(t))throw new G(t);let n=new Je(t);this.jobs.set(t,n);try{n.setPhase("running");let i=await e(n);return n.throwIfCancelled(),i}catch(i){throw n.cancelled&&!(i instanceof T)?new T(t):i}finally{this.jobs.delete(t)}}cancel(t){let e=this.jobs.get(t);return e?e.cancel():!1}};function $e(r){if(r instanceof T)return"cancelled";let t=r;if(t?.code==="cancelled")return"cancelled";let e=String(t?.message||r);return/api key|unauthorized|401|403|认证|权限/i.test(e)?"auth":/timeout|超时|timed out/i.test(e)?"timeout":/429|rate limit|too many requests/i.test(e)?"rate-limit":/非 JSON|json_schema|schema|structured/i.test(e)?"schema":/model 未设置|base url|配置|config/i.test(e)?"config":"unknown"}var me="record_parallel_reader_cards";function Ot(r){let t=[],e=-1,n=0,i=!1,a=!1;for(let s=0;s0&&n--,n===0&&e>=0&&(t.push(r.slice(e,s+1)),e=-1))}return t}function Ke(r){let t=(r||"").trim();if(!t)return t;try{return JSON.parse(t),t}catch{}let e=t.match(/```(?:json)?\s*([\s\S]*?)```/);if(e){let i=e[1].trim();try{return JSON.parse(i),i}catch{}}let n=Ot(t);n.sort((i,a)=>a.length-i.length);for(let i of n)try{return JSON.parse(i),i}catch{}return t}function k(r,t){let e=Ke(r),n;try{n=JSON.parse(e)}catch{throw new Error(w(t||null,"errorLlmNonJson",{excerpt:(r||"").slice(0,500)}))}return W(n)}function W(r){return(r&&Array.isArray(r.cards)?r.cards:[]).filter(e=>!!e&&typeof e=="object").map(e=>({title:typeof e.title=="string"?e.title:"(\u65E0\u6807\u9898)",anchor:typeof e.anchor=="string"?e.anchor:"",gist:typeof e.gist=="string"?e.gist:"",bullets:Array.isArray(e.bullets)?e.bullets.filter(n=>typeof n=="string"):[]}))}function Y(r){let t={type:"object",properties:{title:{type:"string"},anchor:{type:"string"},gist:{type:"string"},bullets:{type:"array",items:{type:"string"}}},required:["title","anchor","gist","bullets"]},e={type:"object",properties:{cards:{type:"array",items:t}},required:["cards"]};return r&&(t.additionalProperties=!1,e.additionalProperties=!1),e}function dt(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:Y(!0)}}}function pt(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:Y(!0)}}}function ht(){return{name:me,description:"Return the generated Parallel Reader summary cards.",input_schema:Y(!0)}}function D(r,t){if(t?.trim())return t.trim();let e=ge.default.homedir(),n=process.platform==="win32",i=n?[".cmd",".exe",""]:[""],a=n?[v.default.join(e,"AppData","Roaming","npm"),v.default.join(e,"AppData","Local","Programs","claude-code"),v.default.join(e,"AppData","Local","Programs","codex"),v.default.join(e,".claude","local"),v.default.join(e,".codex","bin"),v.default.join(e,".bun","bin"),v.default.join(e,"scoop","shims"),"C:\\Program Files\\nodejs"]:[v.default.join(e,"bin"),v.default.join(e,".local","bin"),v.default.join(e,".claude","local"),v.default.join(e,".codex","bin"),v.default.join(e,".bun","bin"),v.default.join(e,".npm-global","bin"),v.default.join(e,".cargo","bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let s of a)for(let o of i){let c=v.default.join(s,r+o);try{if(gt.default.existsSync(c))return c}catch{}}return r}function X(r,t,e,n,i){return new Promise((a,s)=>{let o,c=!1,l=null,u=C=>{c||(c=!0,l&&clearTimeout(l),s(C))},h=C=>{c||(c=!0,l&&clearTimeout(l),a(C))};try{o=(0,mt.spawn)(r,t,{stdio:["pipe","pipe","pipe"],env:{...process.env,PATH:[process.env.PATH||"","/usr/local/bin","/opt/homebrew/bin",v.default.join(ge.default.homedir(),".local/bin"),v.default.join(ge.default.homedir(),".claude/local")].filter(Boolean).join(":")}})}catch(C){return s(new Error(`Failed to start ${r}: ${C.message}`))}let g="",p="";if(l=setTimeout(()=>{try{o.kill("SIGKILL")}catch{}u(new Error(`CLI timed out (${n}ms)`))},n),i&&i.onCancel(()=>{try{o.kill("SIGKILL")}catch{}u(new T(i.key))}),!o.stdout||!o.stderr||!o.stdin){u(new Error("CLI process streams are unavailable"));return}let f=o.stdout,y=o.stderr,B=o.stdin;if(f.on("data",C=>{g+=C.toString("utf8")}),y.on("data",C=>{p+=C.toString("utf8")}),o.on("error",C=>{u(new Error(`CLI startup error: ${C.message}. Try setting an absolute CLI path.`))}),o.on("close",C=>{if(!c){if(C!==0)return u(new Error(`CLI exited with code ${C} stderr: -${d.slice(0,1e3)}`));m({stdout:g,stderr:d})}}),e)try{_.write(e),_.end()}catch{}else try{_.end()}catch{}})}async function pt(r,t,e,n){let i=D("claude",e.cliPath),a=["-p","--output-format","json","--append-system-prompt",r,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"],{stdout:s}=await X(i,a,t,e.cliTimeoutMs,n),o="";try{let l=JSON.parse(s);if(Array.isArray(l)){let c=l.find(u=>u.type==="result");c&&typeof c.result=="string"&&(o=c.result)}else l&&typeof l=="object"&&(o=l.result||l.content||"")}catch{throw new Error(`claude CLI returned unexpected output: +${p.slice(0,1e3)}`));h({stdout:g,stderr:p})}}),e)try{B.write(e),B.end()}catch{}else try{B.end()}catch{}})}async function ft(r,t,e,n){let i=D("claude",e.cliPath),a=["-p","--output-format","json","--append-system-prompt",r,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"],{stdout:s}=await X(i,a,t,e.cliTimeoutMs,n),o="";try{let c=JSON.parse(s);if(Array.isArray(c)){let l=c.find(u=>u.type==="result");l&&typeof l.result=="string"&&(o=l.result)}else c&&typeof c=="object"&&(o=c.result||c.content||"")}catch{throw new Error(`claude CLI returned unexpected output: `+s.slice(0,500))}if(!o)throw new Error(`claude CLI returned no result. Output: -`+s.slice(0,500));return E(o,e)}async function ht(r,t,e,n){let i=D("codex",e.cliPath),a=`<> +`+s.slice(0,500));return k(o,e)}async function yt(r,t,e,n){let i=D("codex",e.cliPath),a=`<> ${r} <> ${t} -Output JSON directly with no explanation.`,s=["exec","--skip-git-repo-check","-"],{stdout:o}=await X(i,s,a,e.cliTimeoutMs,n);return E(o,e)}var bt=require("obsidian");function It(r){return r==="en"?"Write title, gist, and bullets in English.":r==="auto"?"Write title, gist, and bullets in the main language of the source document.":"\u7528\u4E2D\u6587\u8F93\u51FA title\u3001gist \u548C bullets\u3002"}function Dt(r){return r==="en"?`{"cards":[ +Output JSON directly with no explanation.`,s=["exec","--skip-git-repo-check","-"],{stdout:o}=await X(i,s,a,e.cliTimeoutMs,n);return k(o,e)}var xt=require("obsidian");function Bt(r){return r==="en"?"Write title, gist, and bullets in English.":r==="auto"?"Write title, gist, and bullets in the main language of the source document.":"\u7528\u4E2D\u6587\u8F93\u51FA title\u3001gist \u548C bullets\u3002"}function _t(r){return r==="en"?`{"cards":[ {"title":"U-shaped gains","anchor":"Who benefits from AI? Overall, it shifts the score from one to seven","gist":"AI productivity gains form a U shape, with both ends benefiting most","bullets":["Top-paid software managers benefit by accelerating existing work","Low-paid workers use AI to create new side income","Middle-layer specialists gain less because prompt precision is hard to trust","Average reported benefit is 5.1/7, with 42% describing gains as unclear"]} ]}`:`{"cards":[ {"title":"U \u578B\u6536\u76CA\u66F2\u7EBF","anchor":"\u90A3\u8C01\u53C8\u4F1A\u88AB AI \u6240\u53D7\u76CA\uFF1F\u6574\u4F53\u6765\u770B\uFF0C\u5B83\u628A\u6574\u4E2A\u5206\u6570\u53D8\u6210\u4E86\u4E00\u5206\u5230\u4E03\u5206","gist":"AI \u751F\u4EA7\u529B\u6536\u76CA\u5448 U \u578B\uFF0C\u4E24\u7AEF\u53D7\u76CA\u6700\u5927\u3001\u4E2D\u95F4\u5C42\u584C\u9677","bullets":["\u6700\u9AD8\u85AA\u5C97\u4F4D\uFF08\u8F6F\u4EF6\u7BA1\u7406\uFF09\u901A\u8FC7\u52A0\u901F\u65E2\u6709\u5DE5\u4F5C\u53D7\u76CA\u6700\u5927","\u6700\u4F4E\u85AA\u5C97\u4F4D\uFF08\u5916\u5356\u5458\u3001\u56ED\u827A\u5DE5\uFF09\u7528 AI \u5F00\u526F\u4E1A\u521B\u9020\u65B0\u6536\u5165","\u4E2D\u95F4\u5C42\u79D1\u5B66\u5BB6\u3001\u5F8B\u5E08\u6536\u76CA\u6700\u5C11\uFF0C\u90E8\u5206\u56E0\u5BF9 prompt \u7CBE\u5EA6\u4FE1\u4EFB\u4E0D\u8DB3","\u5168\u4F53\u5747\u5206 5.1/7\uFF0C42% \u62A5\u544A\u6536\u76CA\u6A21\u7CCA"]} -]}`}function Lt(r,t){return String(r||"").replace(/\{([a-zA-Z0-9_]+)\}/g,(e,n)=>Object.hasOwn(t,n)?String(t[n]):e)}function Ot(r,t,e,n,i,a){return r==="en"?`You are a long-form reading summary assistant. After reading the full document, split it into ${t}-${e} natural topic units. They do not need to match markdown headings; use a complete argument or topic as the unit, merging short sections and splitting long ones when needed. +]}`}function Vt(r,t){return String(r||"").replace(/\{([a-zA-Z0-9_]+)\}/g,(e,n)=>Object.hasOwn(t,n)?String(t[n]):e)}function jt(r,t,e,n,i,a){return r==="en"?`You are a long-form reading summary assistant. After reading the full document, split it into ${t}-${e} natural topic units. They do not need to match markdown headings; use a complete argument or topic as the unit, merging short sections and splitting long ones when needed. Each card has one guiding sentence plus several bullets. Bullets carry details; gist is the lead-in. @@ -68,7 +68,7 @@ ${a}`:`\u4F60\u662F\u4E00\u4E2A\u957F\u6587\u9605\u8BFB\u6458\u8981\u52A9\u624B\ ${i} \u793A\u4F8B\uFF1A -${a}`}function _t(r,t,e,n,i){return r==="en"?`Non-overridable output contract: +${a}`}function Ut(r,t,e,n,i){return r==="en"?`Non-overridable output contract: - Must output ${t}-${e} cards. - ${n} - anchor must be copied verbatim from the source and exact-substring-match the source. @@ -78,26 +78,26 @@ ${a}`}function _t(r,t,e,n,i){return r==="en"?`Non-overridable output contract: - ${n} - anchor \u5FC5\u987B\u4ECE\u539F\u6587\u9010\u5B57\u590D\u5236\uFF0C\u5FC5\u987B\u80FD\u5728\u539F\u6587 exact substring match \u627E\u5230\u3002 - \u4E25\u683C\u53EA\u8F93\u51FA JSON\uFF0C\u65E0 markdown fence\u3001\u65E0\u89E3\u91CA\u3001\u65E0 tool call\u3002 -- JSON shape: ${i}`}function ge(r,t){let e=Number(t.maxDocChars)||Te,n=$[t.promptLanguage]?t.promptLanguage:b.promptLanguage,i=Math.max(1,Number(t.minCards)||b.minCards),a=Math.max(i,Number(t.maxCards)||b.maxCards),s=It(n),o=r.length>e?r.slice(0,e)+(n==="en"?` +- JSON shape: ${i}`}function fe(r,t){let e=Number(t.maxDocChars)||ke,n=$[t.promptLanguage]?t.promptLanguage:b.promptLanguage,i=Math.max(1,Number(t.minCards)||b.minCards),a=Math.max(i,Number(t.maxCards)||b.maxCards),s=Bt(n),o=r.length>e?r.slice(0,e)+(n==="en"?` [Document truncated]`:` -[\u6587\u6863\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD]`):r,l='{"cards":[{"title":"...","anchor":"...","gist":"...","bullets":["...","..."]}]}',c=Dt(n),u={minCards:i,maxCards:a,languageInstruction:s,schema:l,example:c},m=Lt(t.customSystemPrompt,u).trim(),g=_t(n,i,a,s,l),d=Ot(n,i,a,s,l,c),f=m?`${m} +[\u6587\u6863\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD]`):r,c='{"cards":[{"title":"...","anchor":"...","gist":"...","bullets":["...","..."]}]}',l=_t(n),u={minCards:i,maxCards:a,languageInstruction:s,schema:c,example:l},h=Vt(t.customSystemPrompt,u).trim(),g=Ut(n,i,a,s,c),p=jt(n,i,a,s,c,l),f=h?`${h} -${g}`:d,y=n==="en"?`Source document: +${g}`:p,y=n==="en"?`Source document: ${o}`:`\u4EE5\u4E0B\u662F\u9700\u8981\u5904\u7406\u7684\u6587\u6863\u5168\u6587\uFF1A -${o}`;return{system:f,user:y}}function Vt(r){return r.choices?.[0]?.delta?.content||""}function Bt(r){return r.type==="content_block_delta"&&r.delta?.text||""}function Z(r){switch(r){case"openai-chat":return Vt;case"anthropic-messages":return Bt;default:return null}}function He(r,t){let e=[],n=r.split(` -`),i=n.pop()??"";for(let a of n){let s=a.trim();if(!s||s==="data: [DONE]"||s.startsWith("event:")||!s.startsWith("data:"))continue;let o=s.slice(s.startsWith("data: ")?6:5);try{let l=JSON.parse(o),c=t(l);c&&e.push(c)}catch{}}return{deltas:e,rest:i}}async function jt(r,t,e,n,i,a,s){let o=await globalThis.fetch(r,{method:"POST",headers:t,body:JSON.stringify(e),signal:a});if(!o.ok){let g=await o.text();throw new Error(w(s||null,"errorProviderApiStatus",{label:"Streaming",status:o.status,excerpt:g.slice(0,500)}))}if(!o.body)throw new Error("Response has no body for streaming");let l=o.body.getReader(),c=new TextDecoder,u="",m="";try{for(;;){let{done:g,value:d}=await l.read();if(g)break;m+=c.decode(d,{stream:!0});let f=He(m,n);m=f.rest;for(let y of f.deltas)u+=y;f.deltas.length>0&&i?.({accumulated:u,done:!1})}}finally{l.releaseLock()}return i?.({accumulated:u,done:!0}),u}async function Je(r,t,e,n,i,a,s){if(typeof globalThis.fetch!="function")throw new Error("Streaming requires fetch API");let o=s?.streamingTimeoutMs??12e4,l=new AbortController;a&&(a.aborted?l.abort():a.addEventListener("abort",()=>l.abort(),{once:!0}));let c=null,u=new Promise((m,g)=>{c=setTimeout(()=>{l.abort(),g(new Error(`Streaming timed out after ${o}ms`))},o)});try{return await Promise.race([jt(r,t,e,n,i,l.signal,s),u])}finally{c!==null&&clearTimeout(c)}}function gt(r){let t=Z(r);if(!t)throw new Error(`Streaming not supported for format: ${r}`);return t}function te(r,t){let e=r.replace(/\/+$/,"");for(let n of t)if(e.endsWith(n))return e;return e+t[0]}function Ut(r,t){let e=(r||"").trim();if(!e)return{};if(e.startsWith("{")){let i;try{i=JSON.parse(e)}catch(s){throw new Error(w(t||null,"errorCustomHeadersJsonParse",{error:s.message}))}if(!i||typeof i!="object"||Array.isArray(i))throw new Error(w(t||null,"errorCustomHeadersJsonObject"));let a={};for(let[s,o]of Object.entries(i))typeof o=="string"&&s.trim()&&(a[s.trim()]=o);return a}let n={};for(let i of e.split(/\r?\n/)){let a=i.trim();if(!a||a.startsWith("#"))continue;let s=a.indexOf(":");if(s<=0)throw new Error(w(t||null,"errorCustomHeadersLineFormat"));let o=a.slice(0,s).trim(),l=a.slice(s+1).trim();o&&(n[o]=l)}return n}function Gt(r){let t=Re(r);if(t==="none")return{};let e=at(r);if(!e){let n=(r.apiKeyEnvVar||x(r).envVar||"").trim(),i=n?w(r,"errorApiKeyEnvHint",{envVar:n}):"";throw new Error(w(r,"errorApiKeyMissing",{hint:i}))}return t==="bearer"?{authorization:`Bearer ${e}`}:t==="x-api-key"?{"x-api-key":e}:t==="x-goog-api-key"?{"x-goog-api-key":e}:t==="api-key"?{"api-key":e}:{authorization:`Bearer ${e}`}}function H(r,t){return{"content-type":"application/json",...Gt(r),...t||{},...Ut(r.apiHeaders,r)}}function Ht(r,t,e){if(r.json&&typeof r.json=="object")return r.json;try{return JSON.parse(r.text||"{}")}catch{throw new Error(w(e||null,"errorProviderNonJson",{label:t,excerpt:(r.text||"").slice(0,500)}))}}async function mt(r,t,e,n,i,a){let s;try{s=await r({url:e,method:"POST",headers:n,body:JSON.stringify(i),throw:!1})}catch(o){throw new Error(w(a||null,"errorProviderRequestFailed",{label:t,error:o.message||String(o)}))}if(s.status>=400)throw new Error(w(a||null,"errorProviderApiStatus",{label:t,status:s.status,excerpt:(s.text||"").slice(0,500)}));return Ht(s,t,a)}function Jt(r){let t=String(r?.message??r);return/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(t)?/response_format|json_schema|responseJsonSchema|responseMimeType|tools?|tool_choice|unsupported|unrecognized|unknown|schema/i.test(t):!1}async function be(r,t,e,n,i,a,s){try{return await mt(r,t,e,n,i,s)}catch(o){if(!a||!Jt(o))throw o;return console.warn(`[parallel-reader] ${t} structured output rejected; retrying without structured output`,o),mt(r,t+" fallback",e,n,a,s)}}function $e(r){if(typeof r=="string")return r;if(Array.isArray(r))return r.map(t=>{if(typeof t=="string")return t;if(t&&typeof t=="object"){let e=t;return typeof e.text=="string"?e.text:typeof e.output_text=="string"?e.output_text:""}return""}).join("");if(r&&typeof r=="object"){let t=r;return typeof t.text=="string"?t.text:typeof t.output_text=="string"?t.output_text:""}return""}function $t(r){if(typeof r.output_text=="string")return r.output_text;let t=[],e=n=>{if(n&&typeof n!="string"){if(Array.isArray(n)){n.forEach(e);return}if(typeof n=="object"){let i=n;typeof i.text=="string"&&t.push(i.text),typeof i.output_text=="string"&&t.push(i.output_text),i.type==="output_text"&&typeof i.content=="string"&&t.push(i.content),i.content&&e(i.content),i.output&&e(i.output)}}};return e(r.output),t.join("")}function Ke(r){let t=x(r),e=S[P(r)];return t.tokenLimitField||e?.tokenLimitField||"max_tokens"}function Q(r,t,e,n){let i=!n||n.structured!==!1,a={model:I(e),max_tokens:Number(e.apiMaxTokens)||4096,system:r,messages:[{role:"user",content:t}]};return i&&(a.tools=[ct()],a.tool_choice={type:"tool",name:he}),a}function ee(r,t,e,n){let i=!n||n.structured!==!1,a={model:I(e),messages:[{role:"system",content:r},{role:"user",content:t}]};return a[Ke(e)]=Number(e.apiMaxTokens)||4096,i&&(a.response_format=ot()),a}function fe(r,t,e,n){let i=!n||n.structured!==!1,a={model:I(e),instructions:r,input:t,max_output_tokens:Number(e.apiMaxTokens)||4096};return i&&(a.text=lt()),a}function ye(r,t,e,n){let i=!n||n.structured!==!1,a={temperature:0,maxOutputTokens:Number(e.apiMaxTokens)||4096};return i&&(a.responseMimeType="application/json",a.responseJsonSchema=Y(!1)),{systemInstruction:{parts:[{text:r}]},contents:[{role:"user",parts:[{text:t}]}],generationConfig:a}}function Kt(r,t){let n=(Array.isArray(r?.content)?r.content:[]).find(i=>i&&i.type==="tool_use"&&i.name===he);return n?typeof n.input=="string"?E(n.input,t):n.input&&typeof n.input=="object"?W(n.input):[]:null}async function qt(r,t,e,n){let i=te(F(n),["/messages"]),a=await be(r,"Anthropic-compatible",i,H(n,{"anthropic-version":"2023-06-01"}),Q(t,e,n),Q(t,e,n,{structured:!1}),n),s=Kt(a,n);if(s)return s;let l=(Array.isArray(a.content)?a.content:[]).map(c=>$e(c)).join("").trim();return E(l,n)}async function zt(r,t,e,n){let i=te(F(n),["/chat/completions"]),a=await be(r,"OpenAI-compatible Chat",i,H(n),ee(t,e,n),ee(t,e,n,{structured:!1}),n),o=(Array.isArray(a.choices)?a.choices:[])[0]||{},l=o.message,c=$e(l?.content??o.text??"").trim();return E(c,n)}async function Wt(r,t,e,n){let i=te(F(n),["/responses"]),a=await be(r,"OpenAI Responses",i,H(n),fe(t,e,n),fe(t,e,n,{structured:!1}),n);return E($t(a).trim(),n)}async function Yt(r,t,e,n){let i=encodeURIComponent(I(n)),a=F(n);/:generateContent(?:\?|$)/.test(a)||(a=`${a.replace(/\/+$/,"")}/models/${i}:generateContent`);let s=H(n),o=await be(r,"Google Gemini",a,s,ye(t,e,n),ye(t,e,n,{structured:!1}),n),u=((Array.isArray(o.candidates)?o.candidates:[])[0]||{}).content,g=(Array.isArray(u?.parts)?u.parts:[]).map(d=>$e(d)).join("").trim();return E(g,n)}async function re(r,t,e,n){switch(P(n)){case"openai-chat":return zt(r,t,e,n);case"openai-responses":return Wt(r,t,e,n);case"google-generative-ai":return Yt(r,t,e,n);default:return qt(r,t,e,n)}}function we(r){if(!r.streaming)return!1;let t=P(r);return!!Z(t)}async function Xt(r,t,e,n,i){let a=te(F(e),["/chat/completions"]),s=H(e),o=ee(r,t,e,{structured:!1});o.stream=!0;let l=gt("openai-chat"),c=await Je(a,s,o,l,n,i,e);return E(c.trim(),e)}async function Zt(r,t,e,n,i){let a=te(F(e),["/messages"]),s=H(e,{"anthropic-version":"2023-06-01"}),o=Q(r,t,e,{structured:!1});o.stream=!0;let l=gt("anthropic-messages"),c=await Je(a,s,o,l,n,i,e);return E(c.trim(),e)}async function ft(r,t,e,n,i){let a=P(e);switch(a){case"openai-chat":return Xt(r,t,e,n,i);case"anthropic-messages":return Zt(r,t,e,n,i);default:throw new Error(`Streaming not supported for format: ${a}`)}}async function yt(r,t){await re(r,'\u53EA\u8F93\u51FA JSON\uFF1A{"cards":[]}','\u8FDE\u901A\u6027\u6D4B\u8BD5\uFF1A\u8BF7\u539F\u6837\u8F93\u51FA {"cards":[]}',t);let e=P(t);return`${x(t).label} / ${S[e].label}`}async function qe(r,t,e,n){let{system:i,user:a}=ge(r,t),s;switch(t.backend){case"claude-code":s=await pt(i,a,t,e);break;case"codex":s=await ht(i,a,t,e);break;default:{if(we(t)){let c=new AbortController;e.onCancel(()=>c.abort()),s=await ft(i,a,t,n,c.signal)}else s=await re(bt.requestUrl,i,a,t);break}}let o=s.map(l=>({title:l.title,level:2,anchor:l.anchor,gist:l.gist,startLine:J(r,l.anchor),bullets:l.bullets}));return o.sort((l,c)=>l.startLine<0&&c.startLine<0?0:l.startLine<0?1:c.startLine<0?-1:l.startLine-c.startLine),o}function ze(r,t){return t?.phase==="generating"&&r&&Fe(r.backend)?"cancelRequestedApiInFlight":"cancelRequested"}function We(r){let t=[`## ${r.title}`];if(r.anchor){let e=r.anchor.replace(/\s+/g," ").trim();t.push(`> ${e}`)}return r.gist&&t.push(r.gist),r.bullets&&r.bullets.length>0&&t.push(r.bullets.map(e=>`- ${e}`).join(` +${o}`;return{system:f,user:y}}function Gt(r){return r.choices?.[0]?.delta?.content||""}function Ht(r){return r.type==="content_block_delta"&&r.delta?.text||""}function Z(r){switch(r){case"openai-chat":return Gt;case"anthropic-messages":return Ht;default:return null}}function qe(r,t){let e=[],n=r.split(` +`),i=n.pop()??"";for(let a of n){let s=a.trim();if(!s||s==="data: [DONE]"||s.startsWith("event:")||!s.startsWith("data:"))continue;let o=s.slice(s.startsWith("data: ")?6:5);try{let c=JSON.parse(o),l=t(c);l&&e.push(l)}catch{}}return{deltas:e,rest:i}}async function Jt(r,t,e,n,i,a,s){let o=await globalThis.fetch(r,{method:"POST",headers:t,body:JSON.stringify(e),signal:a});if(!o.ok){let g=await o.text();throw new Error(w(s||null,"errorProviderApiStatus",{label:"Streaming",status:o.status,excerpt:g.slice(0,500)}))}if(!o.body)throw new Error("Response has no body for streaming");let c=o.body.getReader(),l=new TextDecoder,u="",h="";try{for(;;){let{done:g,value:p}=await c.read();if(g)break;h+=l.decode(p,{stream:!0});let f=qe(h,n);h=f.rest;for(let y of f.deltas)u+=y;f.deltas.length>0&&i?.({accumulated:u,done:!1})}}finally{c.releaseLock()}return i?.({accumulated:u,done:!0}),u}async function ze(r,t,e,n,i,a,s){if(typeof globalThis.fetch!="function")throw new Error("Streaming requires fetch API");let o=s?.streamingTimeoutMs??12e4,c=new AbortController;a&&(a.aborted?c.abort():a.addEventListener("abort",()=>c.abort(),{once:!0}));let l=null,u=new Promise((h,g)=>{l=setTimeout(()=>{c.abort(),g(new Error(`Streaming timed out after ${o}ms`))},o)});try{return await Promise.race([Jt(r,t,e,n,i,c.signal,s),u])}finally{l!==null&&clearTimeout(l)}}function wt(r){let t=Z(r);if(!t)throw new Error(`Streaming not supported for format: ${r}`);return t}function te(r,t){let e=r.replace(/\/+$/,"");for(let n of t)if(e.endsWith(n))return e;return e+t[0]}function $t(r,t){let e=(r||"").trim();if(!e)return{};if(e.startsWith("{")){let i;try{i=JSON.parse(e)}catch(s){throw new Error(w(t||null,"errorCustomHeadersJsonParse",{error:s.message}))}if(!i||typeof i!="object"||Array.isArray(i))throw new Error(w(t||null,"errorCustomHeadersJsonObject"));let a={};for(let[s,o]of Object.entries(i))typeof o=="string"&&s.trim()&&(a[s.trim()]=o);return a}let n={};for(let i of e.split(/\r?\n/)){let a=i.trim();if(!a||a.startsWith("#"))continue;let s=a.indexOf(":");if(s<=0)throw new Error(w(t||null,"errorCustomHeadersLineFormat"));let o=a.slice(0,s).trim(),c=a.slice(s+1).trim();o&&(n[o]=c)}return n}function Kt(r){let t=Me(r);if(t==="none")return{};let e=lt(r);if(!e){let n=(r.apiKeyEnvVar||x(r).envVar||"").trim(),i=n?w(r,"errorApiKeyEnvHint",{envVar:n}):"";throw new Error(w(r,"errorApiKeyMissing",{hint:i}))}return t==="bearer"?{authorization:`Bearer ${e}`}:t==="x-api-key"?{"x-api-key":e}:t==="x-goog-api-key"?{"x-goog-api-key":e}:t==="api-key"?{"api-key":e}:{authorization:`Bearer ${e}`}}function H(r,t){return{"content-type":"application/json",...Kt(r),...t||{},...$t(r.apiHeaders,r)}}function qt(r,t,e){if(r.json&&typeof r.json=="object")return r.json;try{return JSON.parse(r.text||"{}")}catch{throw new Error(w(e||null,"errorProviderNonJson",{label:t,excerpt:(r.text||"").slice(0,500)}))}}async function bt(r,t,e,n,i,a){let s;try{s=await r({url:e,method:"POST",headers:n,body:JSON.stringify(i),throw:!1})}catch(o){throw new Error(w(a||null,"errorProviderRequestFailed",{label:t,error:o.message||String(o)}))}if(s.status>=400)throw new Error(w(a||null,"errorProviderApiStatus",{label:t,status:s.status,excerpt:(s.text||"").slice(0,500)}));return qt(s,t,a)}function zt(r){let t=String(r?.message??r);return/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(t)?/response_format|json_schema|responseJsonSchema|responseMimeType|tools?|tool_choice|unsupported|unrecognized|unknown|schema/i.test(t):!1}async function we(r,t,e,n,i,a,s){try{return await bt(r,t,e,n,i,s)}catch(o){if(!a||!zt(o))throw o;return console.warn(`[parallel-reader] ${t} structured output rejected; retrying without structured output`,o),bt(r,t+" fallback",e,n,a,s)}}function We(r){if(typeof r=="string")return r;if(Array.isArray(r))return r.map(t=>{if(typeof t=="string")return t;if(t&&typeof t=="object"){let e=t;return typeof e.text=="string"?e.text:typeof e.output_text=="string"?e.output_text:""}return""}).join("");if(r&&typeof r=="object"){let t=r;return typeof t.text=="string"?t.text:typeof t.output_text=="string"?t.output_text:""}return""}function Wt(r){if(typeof r.output_text=="string")return r.output_text;let t=[],e=n=>{if(n&&typeof n!="string"){if(Array.isArray(n)){n.forEach(e);return}if(typeof n=="object"){let i=n;typeof i.text=="string"&&t.push(i.text),typeof i.output_text=="string"&&t.push(i.output_text),i.type==="output_text"&&typeof i.content=="string"&&t.push(i.content),i.content&&e(i.content),i.output&&e(i.output)}}};return e(r.output),t.join("")}function Ye(r){let t=x(r),e=S[P(r)];return t.tokenLimitField||e?.tokenLimitField||"max_tokens"}function Q(r,t,e,n){let i=!n||n.structured!==!1,a={model:I(e),max_tokens:Number(e.apiMaxTokens)||4096,system:r,messages:[{role:"user",content:t}]};return i&&(a.tools=[ht()],a.tool_choice={type:"tool",name:me}),a}function ee(r,t,e,n){let i=!n||n.structured!==!1,a={model:I(e),messages:[{role:"system",content:r},{role:"user",content:t}]};return a[Ye(e)]=Number(e.apiMaxTokens)||4096,i&&(a.response_format=dt()),a}function ye(r,t,e,n){let i=!n||n.structured!==!1,a={model:I(e),instructions:r,input:t,max_output_tokens:Number(e.apiMaxTokens)||4096};return i&&(a.text=pt()),a}function be(r,t,e,n){let i=!n||n.structured!==!1,a={temperature:0,maxOutputTokens:Number(e.apiMaxTokens)||4096};return i&&(a.responseMimeType="application/json",a.responseJsonSchema=Y(!1)),{systemInstruction:{parts:[{text:r}]},contents:[{role:"user",parts:[{text:t}]}],generationConfig:a}}function Yt(r,t){let n=(Array.isArray(r?.content)?r.content:[]).find(i=>i&&i.type==="tool_use"&&i.name===me);return n?typeof n.input=="string"?k(n.input,t):n.input&&typeof n.input=="object"?W(n.input):[]:null}async function Xt(r,t,e,n){let i=te(F(n),["/messages"]),a=await we(r,"Anthropic-compatible",i,H(n,{"anthropic-version":"2023-06-01"}),Q(t,e,n),Q(t,e,n,{structured:!1}),n),s=Yt(a,n);if(s)return s;let c=(Array.isArray(a.content)?a.content:[]).map(l=>We(l)).join("").trim();return k(c,n)}async function Zt(r,t,e,n){let i=te(F(n),["/chat/completions"]),a=await we(r,"OpenAI-compatible Chat",i,H(n),ee(t,e,n),ee(t,e,n,{structured:!1}),n),o=(Array.isArray(a.choices)?a.choices:[])[0]||{},c=o.message,l=We(c?.content??o.text??"").trim();return k(l,n)}async function Qt(r,t,e,n){let i=te(F(n),["/responses"]),a=await we(r,"OpenAI Responses",i,H(n),ye(t,e,n),ye(t,e,n,{structured:!1}),n);return k(Wt(a).trim(),n)}async function er(r,t,e,n){let i=encodeURIComponent(I(n)),a=F(n);/:generateContent(?:\?|$)/.test(a)||(a=`${a.replace(/\/+$/,"")}/models/${i}:generateContent`);let s=H(n),o=await we(r,"Google Gemini",a,s,be(t,e,n),be(t,e,n,{structured:!1}),n),u=((Array.isArray(o.candidates)?o.candidates:[])[0]||{}).content,g=(Array.isArray(u?.parts)?u.parts:[]).map(p=>We(p)).join("").trim();return k(g,n)}async function re(r,t,e,n){switch(P(n)){case"openai-chat":return Zt(r,t,e,n);case"openai-responses":return Qt(r,t,e,n);case"google-generative-ai":return er(r,t,e,n);default:return Xt(r,t,e,n)}}function Ce(r){if(!r.streaming)return!1;let t=P(r);return!!Z(t)}async function tr(r,t,e,n,i){let a=te(F(e),["/chat/completions"]),s=H(e),o=ee(r,t,e,{structured:!1});o.stream=!0;let c=wt("openai-chat"),l=await ze(a,s,o,c,n,i,e);return k(l.trim(),e)}async function rr(r,t,e,n,i){let a=te(F(e),["/messages"]),s=H(e,{"anthropic-version":"2023-06-01"}),o=Q(r,t,e,{structured:!1});o.stream=!0;let c=wt("anthropic-messages"),l=await ze(a,s,o,c,n,i,e);return k(l.trim(),e)}async function Ct(r,t,e,n,i){let a=P(e);switch(a){case"openai-chat":return tr(r,t,e,n,i);case"anthropic-messages":return rr(r,t,e,n,i);default:throw new Error(`Streaming not supported for format: ${a}`)}}async function vt(r,t){await re(r,'\u53EA\u8F93\u51FA JSON\uFF1A{"cards":[]}','\u8FDE\u901A\u6027\u6D4B\u8BD5\uFF1A\u8BF7\u539F\u6837\u8F93\u51FA {"cards":[]}',t);let e=P(t);return`${x(t).label} / ${S[e].label}`}async function Xe(r,t,e,n){let{system:i,user:a}=fe(r,t),s;switch(t.backend){case"claude-code":s=await ft(i,a,t,e);break;case"codex":s=await yt(i,a,t,e);break;default:{if(Ce(t)){let l=new AbortController;e.onCancel(()=>l.abort()),s=await Ct(i,a,t,n,l.signal)}else s=await re(xt.requestUrl,i,a,t);break}}let o=s.map(c=>({title:c.title,level:2,anchor:c.anchor,gist:c.gist,startLine:J(r,c.anchor),bullets:c.bullets}));return o.sort((c,l)=>c.startLine<0&&l.startLine<0?0:c.startLine<0?1:l.startLine<0?-1:c.startLine-l.startLine),o}function Ze(r,t){return t?.phase==="generating"&&r&&Re(r.backend)?"cancelRequestedApiInFlight":"cancelRequested"}function Qe(r){let t=[`## ${r.title}`];if(r.anchor){let e=r.anchor.replace(/\s+/g," ").trim();t.push(`> ${e}`)}return r.gist&&t.push(r.gist),r.bullets&&r.bullets.length>0&&t.push(r.bullets.map(e=>`- ${e}`).join(` `)),t.join(` -`)}function wt(r){return[r.title,r.gist||"",...(r.bullets||[]).map(t=>"\u2022 "+t)].filter(Boolean).join(` -`)}function ne(r,t){let e=[`# ${r||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let n of t||[])e.push(We(n));return e.join(` +`)}function At(r){return[r.title,r.gist||"",...(r.bullets||[]).map(t=>"\u2022 "+t)].filter(Boolean).join(` +`)}function ne(r,t){let e=[`# ${r||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let n of t||[])e.push(Qe(n));return e.join(` -`)}function Ce(r,t,e){if(!Number.isFinite(t)||t<=0)return-1;let n=Math.floor(t),i=e<0?-1:1;return r<0||r>=n?i<0?n-1:0:Math.min(n-1,Math.max(0,r+i))}function ve(r,t){if(!Array.isArray(r)||t<0||t>=r.length)return-1;let e=Number(r[t]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}function Ye(r,t=80,e=.1){let n=Number(r?.top)||0,i=Math.max(0,Number(r?.height)||0),a=Math.min(Number(t)||0,i*e);return n+Math.max(0,a)}function Qt(r){return typeof requestAnimationFrame=="function"?requestAnimationFrame(r):setTimeout(()=>r(Date.now()),16)}function er(r){if(typeof cancelAnimationFrame=="function"){cancelAnimationFrame(r);return}clearTimeout(r)}function Xe(r,t=Qt,e=er){let n=null,i=()=>{n===null&&(n=t(()=>{n=null,r()}))};return i.cancel=()=>{n!==null&&(e(n),n=null)},i}var h=require("obsidian");async function tr(r){if(r.backend==="claude-code"){let t=D("claude",r.cliPath),{stdout:e}=await X(t,["--version"],"",1e4);return`claude @ ${t} +`)}function ve(r,t,e){if(!Number.isFinite(t)||t<=0)return-1;let n=Math.floor(t),i=e<0?-1:1;return r<0||r>=n?i<0?n-1:0:Math.min(n-1,Math.max(0,r+i))}function xe(r,t){if(!Array.isArray(r)||t<0||t>=r.length)return-1;let e=Number(r[t]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}function et(r,t=80,e=.1){let n=Number(r?.top)||0,i=Math.max(0,Number(r?.height)||0),a=Math.min(Number(t)||0,i*e);return n+Math.max(0,a)}function nr(r){return typeof requestAnimationFrame=="function"?requestAnimationFrame(r):setTimeout(()=>r(Date.now()),16)}function ir(r){if(typeof cancelAnimationFrame=="function"){cancelAnimationFrame(r);return}clearTimeout(r)}function tt(r,t=nr,e=ir){let n=null,i=()=>{n===null&&(n=t(()=>{n=null,r()}))};return i.cancel=()=>{n!==null&&(e(n),n=null)},i}var m=require("obsidian");async function ar(r){if(r.backend==="claude-code"){let t=D("claude",r.cliPath),{stdout:e}=await X(t,["--version"],"",1e4);return`claude @ ${t} ${e.trim()}`}if(r.backend==="codex"){let t=D("codex",r.cliPath),{stdout:e}=await X(t,["--version"],"",1e4);return`codex @ ${t} -${e.trim()}`}return yt(h.requestUrl,r)}var xe=class extends h.PluginSettingTab{constructor(t,e){super(t,e),this.plugin=e}display(){let{containerEl:t}=this,e=(a,s)=>this.plugin.t(a,s);t.empty(),new h.Setting(t).setName(e("settingsTitle")).setHeading(),new h.Setting(t).setName(e("settingUiLanguageName")).setDesc(e("settingUiLanguageDesc")).addDropdown(a=>{for(let[s,o]of Object.entries(Ee))a.addOption(s,o);return a.setValue(this.plugin.settings.uiLanguage||b.uiLanguage).onChange(async s=>{this.plugin.settings.uiLanguage=s,await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(e("settingBackendName")).setDesc(e("settingBackendDesc")).addDropdown(a=>a.addOption("api","API / provider").addOption("claude-code","Claude Code CLI").addOption("codex","Codex CLI").setValue(this.plugin.settings.backend).onChange(async s=>{this.plugin.settings.backend=s,s==="api"&&!this.plugin.settings.apiBaseUrl&&Me(this.plugin.settings,this.plugin.settings.apiProvider||"anthropic"),await this.plugin.saveSettings(),this.display()}));let n=this.plugin.settings.backend==="claude-code"||this.plugin.settings.backend==="codex";if(n&&new h.Setting(t).setName(e("settingCliPathName")).setDesc(e("settingCliPathDesc")).addText(a=>a.setPlaceholder(e("settingCliPathPlaceholder")).setValue(this.plugin.settings.cliPath).onChange(s=>{this.plugin.settings.cliPath=s.trim(),this.plugin.saveSettingsDebounced()})),n||new h.Setting(t).setName(e("apiProviderHeader")).setHeading(),!n){let a=x(this.plugin.settings);new h.Setting(t).setName(e("settingProviderPresetName")).setDesc(e("settingProviderPresetDesc")).addDropdown(s=>{for(let[o,l]of Object.entries(R))s.addOption(o,l.label);return s.setValue(this.plugin.settings.apiProvider).onChange(async o=>{Me(this.plugin.settings,o),await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(e("settingApiFormatName")).setDesc(e("settingApiFormatDesc")).addDropdown(s=>{for(let[o,l]of Object.entries(S))s.addOption(o,l.label);return s.setValue(P(this.plugin.settings)).onChange(async o=>{this.plugin.settings.apiFormat=o,await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(e("settingBaseUrlName")).setDesc(e("settingBaseUrlDesc")).addText(s=>s.setPlaceholder((this.plugin.settings.apiProvider||"").startsWith("custom-")?"https://your-provider.example/v1":a.baseUrl||S[P(this.plugin.settings)].defaultBaseUrl).setValue(this.plugin.settings.apiBaseUrl).onChange(o=>{this.plugin.settings.apiBaseUrl=o.trim(),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(e("settingApiKeyName")).setDesc(e("settingApiKeyDesc")).addText(s=>(s.inputEl.type="password",s.setPlaceholder("sk-...").setValue(this.plugin.settings.apiKey).onChange(o=>{this.plugin.settings.apiKey=o.trim(),this.plugin.saveSettingsDebounced()}))),new h.Setting(t).setName(e("settingApiKeyEnvName")).setDesc(e("settingApiKeyEnvDesc")).addText(s=>s.setPlaceholder(a.envVar||"OPENAI_API_KEY").setValue(this.plugin.settings.apiKeyEnvVar).onChange(o=>{this.plugin.settings.apiKeyEnvVar=o.trim(),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(e("settingAuthTypeName")).setDesc(e("settingAuthTypeDesc")).addDropdown(s=>{for(let[o,l]of Object.entries(ke))s.addOption(o,l);return s.setValue(this.plugin.settings.apiAuthType||"auto").onChange(async o=>{this.plugin.settings.apiAuthType=o,await this.plugin.saveSettings()})}),new h.Setting(t).setName(e("settingHeadersName")).setDesc(e("settingHeadersDesc")).addTextArea(s=>s.setPlaceholder("cf-aig-authorization: Bearer ...").setValue(this.plugin.settings.apiHeaders).onChange(o=>{this.plugin.settings.apiHeaders=o,this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(e("settingMaxTokensName")).addText(s=>s.setValue(String(this.plugin.settings.apiMaxTokens)).onChange(o=>{let l=parseInt(o,10);!Number.isNaN(l)&&l>0&&(this.plugin.settings.apiMaxTokens=l,this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(e("settingStreamingName")).setDesc(e("settingStreamingDesc")).addToggle(s=>s.setValue(this.plugin.settings.streaming??!0).onChange(async o=>{this.plugin.settings.streaming=o,await this.plugin.saveSettings()}))}new h.Setting(t).setName(e("settingModelName")).setDesc(e(n?"settingModelDescCli":"settingModelDescApi")).addText(a=>a.setPlaceholder(n?b.model:x(this.plugin.settings).model||"model-id").setValue(this.plugin.settings.model).onChange(s=>{this.plugin.settings.model=s.trim()||(n?b.model:""),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(e("settingMaxInputName")).setDesc(e("settingMaxInputDesc")).addText(a=>a.setValue(String(this.plugin.settings.maxDocChars||b.maxDocChars)).onChange(s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>=1e3&&(this.plugin.settings.maxDocChars=o,this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(e("promptHeader")).setHeading(),new h.Setting(t).setName(e("settingPromptLanguageName")).setDesc(e("settingPromptLanguageDesc")).addDropdown(a=>{for(let[s,o]of Object.entries($))a.addOption(s,o);return a.setValue(this.plugin.settings.promptLanguage||b.promptLanguage).onChange(async s=>{this.plugin.settings.promptLanguage=s,await this.plugin.saveSettings()})}),new h.Setting(t).setName(e("settingCardRangeName")).setDesc(e("settingCardRangeDesc")).addText(a=>a.setPlaceholder("min").setValue(String(this.plugin.settings.minCards||b.minCards)).onChange(s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.minCards=o,this.plugin.settings.maxCardsa.setPlaceholder("max").setValue(String(this.plugin.settings.maxCards||b.maxCards)).onChange(s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.maxCards=Math.max(o,this.plugin.settings.minCards||b.minCards),this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(e("settingCustomPromptName")).setDesc(e("settingCustomPromptDesc")).addTextArea(a=>(a.inputEl.rows=8,a.setPlaceholder(e("settingCustomPromptPlaceholder")).setValue(this.plugin.settings.customSystemPrompt||"").onChange(s=>{this.plugin.settings.customSystemPrompt=s,this.plugin.saveSettingsDebounced()}))),new h.Setting(t).setName(e("settingTestBackendName")).setDesc(e(n?"settingTestBackendDescCli":"settingTestBackendDescApi")).addButton(a=>a.setButtonText("Test").onClick(async()=>{try{let s=await tr(this.plugin.settings);new h.Notice(`\u2713 ${s.slice(0,180)}`,8e3)}catch(s){new h.Notice(e("backendTestFailed",{error:s.message}),1e4)}})),new h.Setting(t).setName(e("settingExportFolderName")).setDesc(e("settingExportFolderDesc")).addText(a=>a.setValue(this.plugin.settings.exportFolder).onChange(s=>{this.plugin.settings.exportFolder=s.trim()||b.exportFolder,this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(e("cacheHeader")).setHeading(),new h.Setting(t).setName(e("settingMaxCacheName")).setDesc(e("settingMaxCacheDesc")).addText(a=>{a.setValue(String(this.plugin.settings.maxCacheEntries||j));let s=async()=>{let o=parseInt(a.getValue(),10);if(Number.isFinite(o)&&o>0){this.plugin.settings.maxCacheEntries=o,await this.plugin.saveSettings();let l=await this.plugin.pruneCacheIfNeeded();l.length>0&&new h.Notice(e("cachePruned",{count:l.length})),this.display()}};return a.inputEl.addEventListener("change",()=>{s()}),a.inputEl.addEventListener("keydown",o=>{o.key==="Enter"&&a.inputEl.blur()}),a});let i=Object.keys(this.plugin.cache).length;new h.Setting(t).setName(e("cachedNotesName",{count:i})).setDesc(e("cachedNotesDesc")).addButton(a=>a.setButtonText(e("clearAllCacheButton")).setWarning().onClick(async()=>{let s=Object.keys(this.plugin.cache).length;await this.plugin.cacheClear(),new h.Notice(e("cacheClearedAll",{count:s})),this.display()}))}};var k=require("obsidian");function M(r,t,e,n){let i=r.createEl("button",{cls:"parallel-reader-icon-button",attr:{type:"button","aria-label":e}});return i.title=e,typeof k.setIcon=="function"?(0,k.setIcon)(i,t):i.textContent=e,i.addEventListener("click",a=>{a.preventDefault(),a.stopPropagation(),Promise.resolve(n()).catch(s=>{console.error(s),new k.Notice(`${e} failed: ${s instanceof Error?s.message:String(s)}`)})}),i}function N(r,t,e,n,i){let a=r.createEl("button",{cls:i||"parallel-reader-text-button",attr:{type:"button"}});return t&&typeof k.setIcon=="function"&&(0,k.setIcon)(a,t),a.createSpan({text:e}),a.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),Promise.resolve(n()).catch(o=>{console.error(o),new k.Notice(`${e} failed: ${o instanceof Error?o.message:String(o)}`)})}),a}async function L(r,t){try{await navigator.clipboard.writeText(r),new k.Notice(t)}catch(e){new k.Notice(`Copy failed: ${e instanceof Error?e.message:String(e)}`)}}function Ze(r){return String(r||"").split("/").map(t=>t.trim()).filter(t=>!!t&&t!==".."&&t!==".").join("/")}function Qe(r){let t=Ze(r);if(!t)return[];let e=t.split("/");return e.map((n,i)=>e.slice(0,i+1).join("/"))}async function Ct(r,t){for(let e of Qe(t))if(!r.vault.getAbstractFileByPath(e))try{await r.vault.createFolder(e)}catch(n){if(!r.vault.getAbstractFileByPath(e))throw n}}var A=require("obsidian");var vt=require("obsidian");var Ae=class extends vt.Modal{constructor(t,e,n,i){super(t),this.plugin=e,this.card=n||{},this.onSave=i}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:this.plugin.t("editCardTitle")});let e=this.createLabeledInput(t,this.plugin.t("editCardTitleField"),this.card.title||""),n=this.createLabeledTextarea(t,this.plugin.t("editCardGistField"),this.card.gist||"",3),i=this.createLabeledTextarea(t,this.plugin.t("editCardBulletsField"),(this.card.bullets||[]).join(` -`),8),a=t.createDiv({cls:"parallel-reader-modal-actions"});N(a,null,this.plugin.t("editCardCancel"),()=>this.close(),"parallel-reader-text-button"),N(a,null,this.plugin.t("editCardSave"),async()=>{await this.onSave({title:e.value.trim()||this.card.title||"",gist:n.value.trim(),bullets:i.value.split(/\r?\n/).map(s=>s.trim()).filter(Boolean)}),this.close()},"parallel-reader-text-button")}createLabeledInput(t,e,n){let i=t.createDiv({cls:"parallel-reader-modal-field"});i.createEl("label",{text:e});let a=i.createEl("input",{attr:{type:"text"}});return a.value=n,a}createLabeledTextarea(t,e,n,i){let a=t.createDiv({cls:"parallel-reader-modal-field"});a.createEl("label",{text:e});let s=a.createEl("textarea");return s.rows=i,s.value=n,s}};var O="parallel-reader-view",Pe=class extends A.ItemView{constructor(e,n){super(e);this.stale=!1;this.loadingMessage="";this.errorMessage="";this.plugin=n,this.sections=[],this.sourceFile=null,this.cards=[],this.activeIdx=-1}getViewType(){return O}getDisplayText(){return this.plugin.t("displayName")}getIcon(){return"book-open"}onOpen(){let e=this.containerEl.children[1];return e.empty(),e.addClass("parallel-reader-container"),e.setAttr("tabindex","0"),e.addEventListener("keydown",n=>this.handleKeydown(n)),this.renderEmpty(),this.focusSummaryPane(),Promise.resolve()}onClose(){return Promise.resolve()}renderEmpty(){this.sourceFile=null,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage="";let e=this.containerEl.children[1];e.empty();let n=e.createDiv({cls:"parallel-reader-empty"});n.createEl("h3",{text:this.plugin.t("appTitle")}),n.createEl("p",{text:this.plugin.t("emptyOpenNote")}),n.createEl("code",{text:this.plugin.t("commandGenerate")})}focusSummaryPane(){let e=this.containerEl.children[1];return!e||typeof e.focus!="function"?!1:(e.focus({preventScroll:!0}),!0)}loadFor(e,n,i){this.sourceFile=e,this.sections=n,this.stale=!!i,this.loadingMessage="",this.errorMessage="",this.render()}renderLoading(e,n){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage=n||this.plugin.t("loadingDefault"),this.errorMessage="",this.render()}renderStreamingPreview(e,n){this.sourceFile?.path!==e.path&&(this.sourceFile=e);let i=this.containerEl.children[1],a=i.querySelector(".parallel-reader-streaming-preview");if(a){let g=a.querySelector("pre");g&&(g.textContent=n.slice(-2e3));let d=a.querySelector(".parallel-reader-stream-counter");d&&(d.textContent=`${n.length} chars`);return}i.empty();let o=i.createDiv({cls:"parallel-reader-header"}).createDiv({cls:"parallel-reader-header-row"});o.createEl("div",{text:e.basename,cls:"parallel-reader-title"});let l=o.createDiv({cls:"parallel-reader-actions"});M(l,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(e)});let c=i.createDiv({cls:"parallel-reader-state parallel-reader-loading parallel-reader-streaming-preview"});c.createDiv({cls:"parallel-reader-spinner"});let u=c.createEl("div",{cls:"parallel-reader-state-title"});u.createSpan({text:this.plugin.t("loadingGenerating")+" "}),u.createSpan({cls:"parallel-reader-stream-counter",text:`${n.length} chars`});let m=c.createEl("pre",{cls:"parallel-reader-stream-text"});m.textContent=n.slice(-2e3)}renderError(e,n){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage=n||this.plugin.t("errorTitle"),this.render()}renderEmptyWithHint(e){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage="";let n=this.containerEl.children[1];n.empty();let i=n.createDiv({cls:"parallel-reader-empty"});i.createEl("h3",{text:e.basename}),i.createEl("p",{text:this.plugin.t("emptyNoCache")}),i.createEl("code",{text:this.plugin.t("commandGenerate")})}render(){let e=this.containerEl.children[1];e.empty();let i=e.createDiv({cls:"parallel-reader-header"}).createDiv({cls:"parallel-reader-header-row"});i.createEl("div",{text:this.sourceFile?.basename||"",cls:"parallel-reader-title"});let a=i.createDiv({cls:"parallel-reader-actions"});if(this.sourceFile&&(this.plugin.isGeneratingFile(this.sourceFile)?M(a,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(this.sourceFile)}):M(a,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0)),M(a,"copy",this.plugin.t("actionCopyAll"),()=>this.plugin.copyCurrentViewMarkdown()),M(a,"download",this.plugin.t("actionExport"),()=>this.exportToVault())),this.stale){let l=e.createDiv({cls:"parallel-reader-stale-banner"});l.createSpan({text:this.plugin.t("staleBanner")}),N(l,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-stale-button")}if(this.loadingMessage){let l=e.createDiv({cls:"parallel-reader-state parallel-reader-loading"});l.createDiv({cls:"parallel-reader-spinner"}),l.createEl("div",{text:this.loadingMessage,cls:"parallel-reader-state-title"}),l.createEl("div",{text:this.plugin.t("loadingSubtitle"),cls:"parallel-reader-state-subtitle"});return}if(this.errorMessage){let l=e.createDiv({cls:"parallel-reader-state parallel-reader-error"});l.createEl("div",{text:this.plugin.t("errorTitle"),cls:"parallel-reader-state-title"}),l.createEl("div",{text:this.errorMessage,cls:"parallel-reader-state-subtitle"}),N(l,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-text-button");return}let s=e.createDiv({cls:"parallel-reader-cards"});this.cards=[];let o=this.sourceFile?.path||"";this.sections.forEach((l,c)=>{let u=s.createDiv({cls:"parallel-reader-card"});u.dataset.idx=String(c),l.startLine<0&&u.addClass("parallel-reader-card-unanchored");let m=u.createEl("div",{cls:"parallel-reader-card-title"});if(m.createSpan({text:l.title}),l.startLine<0&&m.createEl("span",{text:" \u26A0",cls:"parallel-reader-warn",title:this.plugin.t("anchorMismatch")}),l.gist){let d=u.createEl("div",{cls:"parallel-reader-gist"});A.MarkdownRenderer.render(this.app,l.gist,d,o,this).catch(()=>{d.setText(l.gist)})}let g=l.bullets||[];if(g.length>0){let d=u.createEl("div",{cls:"parallel-reader-bullets-md"}),f=g.map(y=>`- ${y}`).join(` -`);A.MarkdownRenderer.render(this.app,f,d,o,this).catch(()=>{d.setText(f)})}else l.gist||u.createEl("div",{cls:"parallel-reader-empty-li",text:this.plugin.t("emptyCard")});u.addEventListener("click",d=>{let f=window.getSelection();if(f&&f.toString().length>0)return;let y=d.target;y&&y.tagName==="A"||l.startLine>=0&&this.plugin.scrollEditorToLine(l.startLine,this.sourceFile)}),u.addEventListener("contextmenu",d=>{d.preventDefault();let f=new A.Menu;f.addItem(y=>y.setTitle(this.plugin.t("menuCopyMarkdown")).setIcon("copy").onClick(()=>L(We(l),this.plugin.t("copiedMarkdown")))),f.addItem(y=>y.setTitle(this.plugin.t("menuCopyPlain")).setIcon("clipboard-copy").onClick(()=>L(wt(l),this.plugin.t("copiedPlain")))),l.anchor&&f.addItem(y=>y.setTitle(this.plugin.t("menuCopyAnchor")).setIcon("quote-glyph").onClick(()=>L(l.anchor,this.plugin.t("copiedAnchor")))),f.addSeparator(),l.startLine>=0&&f.addItem(y=>y.setTitle(this.plugin.t("menuJumpSource")).setIcon("arrow-right").onClick(()=>this.plugin.scrollEditorToLine(l.startLine,this.sourceFile))),f.addSeparator(),f.addItem(y=>y.setTitle(this.plugin.t("menuEditCard")).setIcon("pencil").onClick(()=>this.openEditCardModal(c))),f.addItem(y=>y.setTitle(this.plugin.t("menuDeleteCard")).setIcon("trash").onClick(()=>this.deleteCard(c))),f.showAtMouseEvent(d)}),this.cards.push(u)}),this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].addClass("is-active")}setActiveSection(e){e!==this.activeIdx&&(this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].removeClass("is-active"),this.activeIdx=e,e>=0&&this.cards[e]&&(this.cards[e].addClass("is-active"),this.cards[e].scrollIntoView({block:"nearest",behavior:"smooth"})))}moveActiveSection(e){let n=Ce(this.activeIdx,this.sections.length,e);return this.setActiveSection(n),this.focusSummaryPane(),n}jumpToActiveSection(){let e=ve(this.sections,this.activeIdx);return e<0||!this.sourceFile?-1:(this.plugin.scrollEditorToLine(e,this.sourceFile),e)}handleKeydown(e){if(e.altKey&&e.key==="ArrowUp"){e.preventDefault(),this.moveActiveSection(-1);return}if(e.altKey&&e.key==="ArrowDown"){e.preventDefault(),this.moveActiveSection(1);return}!e.altKey&&!e.metaKey&&!e.ctrlKey&&!e.shiftKey&&e.key==="Enter"&&this.jumpToActiveSection()>=0&&e.preventDefault()}async deleteCard(e){if(!this.sourceFile)return!1;let n=ue(this.sections,e);if(n.length===this.sections.length)return!1;let i=this.sections.length;return this.sections=n,this.activeIdx=de(e,i,this.activeIdx),await this.plugin.cacheReplaceCards(this.sourceFile.path,n),this.render(),new A.Notice(this.plugin.t("cardDeleted")),!0}openEditCardModal(e){return!this.sourceFile||!this.sections[e]?!1:(new Ae(this.app,this.plugin,this.sections[e],async n=>{await this.updateCard(e,n)}).open(),!0)}async updateCard(e,n){if(!this.sourceFile)return!1;let i=pe(this.sections,e,n);return i.length!==this.sections.length?!1:(this.sections=i,await this.plugin.cacheReplaceCards(this.sourceFile.path,i),this.render(),new A.Notice(this.plugin.t("cardSaved")),!0)}async exportToVault(){if(!this.sourceFile)return;let e=Ze(this.plugin.settings.exportFolder),n=`${this.sourceFile.basename} - ${this.plugin.t("displayName")}.md`,i=`${e}/${n}`,a=["---",`source: [[${this.sourceFile.basename}]]`,`generated: ${new Date().toISOString().slice(0,10)}`,"tool: parallel-reader","---","",ne(`${this.sourceFile.basename} \xB7 ${this.plugin.t("displayName")}`,this.sections),""].join(` -`),s=this.plugin.app;await Ct(s,e);let o=s.vault.getAbstractFileByPath(i);o instanceof A.TFile?await s.vault.modify(o,a):await s.vault.create(i,a),new A.Notice(this.plugin.t("exported",{path:i}))}};var et=class extends p.Plugin{constructor(){super(...arguments);this._scrollDispose=null;this._settingsSaveTimer=null}get cache(){return this.cacheManager.cache}t(e,n){return w(this.settings||b,e,n)}async onload(){await this.loadSettings(),this.jobs=new z,this.addRibbonIcon("book-open",this.t("ribbonOpen"),async()=>{let e=this.getActiveView();await this.ensureView()&&e?.file&&await this.syncViewToFile(e.file)}),this.registerView(O,e=>new Pe(e,this)),this.addCommand({id:"run",name:this.t("cmdRun"),callback:()=>this.runForActiveFile(!1)}),this.addCommand({id:"regen",name:this.t("cmdRegen"),callback:()=>this.runForActiveFile(!0)}),this.addCommand({id:"open-view",name:this.t("cmdOpenView"),callback:async()=>{let e=this.getActiveView();await this.ensureView()&&e?.file&&this.syncViewToFile(e.file)}}),this.addCommand({id:"export-current",name:this.t("cmdExport"),callback:()=>this.exportCurrentView()}),this.addCommand({id:"copy-current-markdown",name:this.t("cmdCopyMarkdown"),callback:()=>this.copyCurrentViewMarkdown()}),this.addCommand({id:"cancel-current",name:this.t("cmdCancel"),callback:()=>this.cancelActiveGeneration()}),this.addCommand({id:"clear-current",name:this.t("cmdClearCurrent"),callback:async()=>{let e=this.getActiveView();if(!e?.file)return new p.Notice(this.t("noCurrentNote"));await this.cacheManager.delete(e.file.path),new p.Notice(this.t("cacheClearedFile",{name:e.file.basename}))}}),this.addCommand({id:"clear-all",name:this.t("cmdClearAll"),callback:async()=>{let e=Object.keys(this.cacheManager.cache).length;await this.cacheManager.clear(),new p.Notice(this.t("cacheClearedAll",{count:e}))}}),this.addCommand({id:"card-prev",name:this.t("cmdCardPrev"),callback:()=>this.moveActiveCard(-1)}),this.addCommand({id:"card-next",name:this.t("cmdCardNext"),callback:()=>this.moveActiveCard(1)}),this.addCommand({id:"card-jump",name:this.t("cmdCardJump"),callback:()=>this.jumpActiveCard()}),this.addCommand({id:"batch-generate",name:this.t("cmdBatchGenerate"),callback:()=>this.runBatchForFolder()}),this.addSettingTab(new xe(this.app,this)),this.registerEvent(this.app.workspace.on("active-leaf-change",()=>this.bindScrollSync())),this.registerEvent(this.app.workspace.on("file-open",e=>{e&&this.syncViewToFile(e)})),this.registerEvent(this.app.workspace.on("file-menu",(e,n)=>this.addFileMenuItems(e,n))),this.registerEvent(this.app.vault.on("rename",(e,n)=>{e instanceof p.TFile&&this.handleFileRename(e,n)})),this.registerEvent(this.app.vault.on("delete",e=>{e instanceof p.TFile&&this.handleFileDelete(e)})),this.bindScrollSync()}onunload(){this.flushSettingsSave(),this.flushCacheSave()}async loadSettings(){let n=(await this.loadData()||{}).settings||{};this.settings=Ne(Object.assign({},b,n)),this.cacheManager=new q(this.app.vault.adapter,this.app.vault.configDir,this.manifest?.id||"parallel-reader",()=>this.settings),await this.cacheManager.load()}async saveSettings(){this._settingsSaveTimer&&(clearTimeout(this._settingsSaveTimer),this._settingsSaveTimer=null),await this.saveData({settings:this.settings})}saveSettingsDebounced(e=400){this._settingsSaveTimer&&clearTimeout(this._settingsSaveTimer),this._settingsSaveTimer=setTimeout(()=>{this._settingsSaveTimer=null,this.saveSettings().catch(n=>console.error("[parallel-reader] failed to save settings",n))},e)}async flushSettingsSave(){this._settingsSaveTimer&&(clearTimeout(this._settingsSaveTimer),this._settingsSaveTimer=null,await this.saveSettings())}cacheTouch(e){return this.cacheManager.touch(e)}scheduleCacheSave(e=5e3){this.cacheManager.scheduleSave(e)}async flushCacheSave(){await this.cacheManager.flush()}async cacheReplaceCards(e,n){return this.cacheManager.replaceCards(e,n)}async cacheClear(){return this.cacheManager.clear()}async pruneCacheIfNeeded(){return this.cacheManager.pruneIfNeeded()}async ensureView(){let{workspace:e}=this.app,n=e.getLeavesOfType(O)[0];if(!n){let i=e.getRightLeaf(!1);if(!i)return new p.Notice(this.t("viewOpenFailed")),null;n=i,await n.setViewState({type:O,active:!0})}return await e.revealLeaf(n),n.view}getActiveView(){return this.app.workspace.getActiveViewOfType(p.MarkdownView)}getParallelView(){return this.app.workspace.getLeavesOfType(O)[0]?.view}moveActiveCard(e){let n=this.getParallelView();return n?.sections.length?n.moveActiveSection(e):(new p.Notice(this.t("noActiveCard")),-1)}jumpActiveCard(){let e=this.getParallelView();return!e||e.jumpToActiveSection()<0?(new p.Notice(this.t("noActiveCard")),!1):!0}isGeneratingFile(e){return!!e&&!!e.path&&this.jobs.isRunning(e.path)}cancelGenerationForFile(e){if(!e?.path)return new p.Notice(this.t("noCancelableJob")),!1;let n=this.jobs.get(e.path),i=ze(this.settings,n),a=this.jobs.cancel(e.path);return new p.Notice(a?this.t(i):this.t("noCancelableJob")),a}viewIsShowingFile(e,n){return!!e&&!!n&&e.sourceFile?.path===n.path}activeFileStillMatches(e){let n=this.getActiveView();return!n?.file||n.file.path===e.path}cancelActiveGeneration(){let e=this.getActiveView();if(e?.file&&this.cancelGenerationForFile(e.file))return;let n=this.getParallelView();n?.sourceFile?this.cancelGenerationForFile(n.sourceFile):new p.Notice(this.t("noCancelableJob"))}confirmRegenerateEditedCards(){return new Promise(e=>{let n=this.t("confirmRegenerateEditedCards"),i=new p.Modal(this.app);i.titleEl.setText(this.t("displayName")),i.contentEl.createEl("p",{text:n});let a=i.contentEl.createDiv({cls:"modal-button-container"});a.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{i.close(),e(!1)}),a.createEl("button",{text:"OK",cls:"mod-cta"}).addEventListener("click",()=>{i.close(),e(!0)}),i.onClose=()=>e(!1),i.open()})}addFileMenuItems(e,n){!(n instanceof p.TFile)||!n.path.endsWith(".md")||(e.addSeparator(),e.addItem(i=>i.setTitle(this.t("fileMenuGenerate")).setIcon("book-open").onClick(()=>this.runForFile(n,!1))),e.addItem(i=>i.setTitle(this.t("fileMenuRegen")).setIcon("refresh-cw").onClick(()=>this.runForFile(n,!0))),this.cacheManager.get(n.path)&&e.addItem(i=>i.setTitle(this.t("fileMenuClear")).setIcon("trash").onClick(async()=>{await this.cacheManager.delete(n.path),new p.Notice(this.t("cacheClearedFile",{name:n.basename}))})))}async handleFileRename(e,n){if(!(e instanceof p.TFile)||!n)return;let i=n.endsWith(".md"),a=e.path.endsWith(".md");if(!i&&!a)return;if(i&&!a){await this.cacheManager.delete(n);let o=this.getParallelView();o&&o.sourceFile?.path===n&&o.renderEmpty();return}if(!i)return;await this.cacheManager.move(n,e.path);let s=this.getParallelView();s?.sourceFile&&(s.sourceFile.path===n||s.sourceFile.path===e.path)&&(s.sourceFile=e,await this.syncViewToFile(e))}async handleFileDelete(e){if(!(e instanceof p.TFile))return;await this.cacheManager.delete(e.path);let n=this.getParallelView();n?.sourceFile?.path===e.path&&n.renderEmpty()}async exportCurrentView(){let e=this.getActiveView(),n=await this.ensureView();if(n){if((!n.sourceFile||!n.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!n.sourceFile||!n.sections.length){new p.Notice(this.t("noExportContent"));return}await n.exportToVault()}}async copyCurrentViewMarkdown(){let e=this.getActiveView(),n=await this.ensureView();if(n){if((!n.sourceFile||!n.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!n.sourceFile||!n.sections.length){new p.Notice(this.t("noCopyContent"));return}await L(ne(`${n.sourceFile.basename} \xB7 ${this.t("displayName")}`,n.sections),this.t("copiedAllMarkdown"))}}async runForActiveFile(e){let n=this.getActiveView();if(!n?.file){new p.Notice(this.t("openNoteFirst"));return}return this.runForFile(n.file,e)}async runForFile(e,n){if(!e){new p.Notice(this.t("openNoteFirst"));return}if(this.jobs.isRunning(e.path)){new p.Notice(this.t("alreadyGenerating"));return}if(Be(this.cacheManager.get(e.path),n)&&!await this.confirmRegenerateEditedCards()){new p.Notice(this.t("regenerateCancelled"));return}let i=null;return this.jobs.start(e.path,async a=>{a.setPhase("reading");let s=await this.app.vault.read(e);if(a.throwIfCancelled(),!s.trim()){new p.Notice(this.t("emptyNote"));return}if(i=await this.ensureView(),!i)return;if(a.throwIfCancelled(),a.setPhase("cache-check"),!n){let d=this.cacheManager.get(e.path);if(d&&U(d,s,this.settings)){await this.cacheTouch(e.path),this.activeFileStillMatches(e)&&await i.loadFor(e,this.resolveCardAnchors(s,d.cards),!1);return}}await i.renderLoading(e,this.t("loadingGenerating"));let o=Number(this.settings.maxDocChars)||b.maxDocChars;s.length>o&&new p.Notice(this.t("longNoteTruncated",{count:o})),new p.Notice(this.t("generatingNotice")),a.setPhase("generating");let l=i,c=l?d=>{!d.done&&this.viewIsShowingFile(l,e)&&l.renderStreamingPreview(e,d.accumulated)}:void 0,u=await qe(s,this.settings,a,c);if(a.throwIfCancelled(),u.length===0){new p.Notice(this.t("noCardsReturned"));return}let m=u.map(d=>({title:d.title,anchor:d.anchor,gist:d.gist,bullets:d.bullets}));a.setPhase("saving"),await this.cacheManager.put(e.path,s,m,this.settings),a.throwIfCancelled(),this.viewIsShowingFile(i,e)&&await i.loadFor(e,u,!1);let g=u.filter(d=>d.startLine<0).length;new p.Notice(this.t("generationDone",{count:u.length,suffix:g?this.t("unanchoredSuffix",{count:g}):""}))}).catch(async a=>{if(a instanceof G){new p.Notice(a.message);return}if(a instanceof T){i&&this.viewIsShowingFile(i,e)&&await i.renderError(e,this.t("cancelledError")),new p.Notice(this.t("cancelled"));return}let s=Ue(a);console.error(a),i&&this.viewIsShowingFile(i,e)&&await i.renderError(e,a.message||String(a)),new p.Notice(this.t("generationFailed",{kind:s==="unknown"?"":` (${s})`,error:a.message||a}))})}async runBatchForFolder(){let e=this,n=await new Promise(s=>{class o extends p.Modal{onOpen(){this.contentEl.createEl("p",{text:e.t("batchSelectFolder")}),this.input=this.contentEl.createEl("input",{type:"text"}),this.input.placeholder=e.t("batchFolderPrompt"),this.input.style.width="100%",this.input.addEventListener("keydown",c=>{c.key==="Enter"&&(s(this.input.value.trim()),this.close())}),this.contentEl.createEl("button",{text:"OK"}).addEventListener("click",()=>{s(this.input.value.trim()),this.close()})}onClose(){s(null)}}new o(e.app).open()});if(n===null)return;let i=De(this.app.vault.getMarkdownFiles(),n);if(i.length===0){new p.Notice(this.t("batchNoMarkdown"));return}let a=Oe(i.length);for(let s=0;s({title:a.title,level:2,anchor:a.anchor,gist:a.gist,startLine:J(e,a.anchor),bullets:a.bullets||[]}));return i.sort((a,s)=>a.startLine<0&&s.startLine<0?0:a.startLine<0?1:s.startLine<0?-1:a.startLine-s.startLine),i}async syncViewToFile(e){if(!e?.path?.endsWith(".md"))return;let n=this.app.workspace.getLeavesOfType(O);if(n.length===0)return;let i=n[0].view;if(!i||!this.activeFileStillMatches(e))return;let a=this.cacheManager.get(e.path);if(!a){await i.loadFor(e,[],!1),i.renderEmptyWithHint(e);return}let s=await this.app.vault.read(e);if(!this.activeFileStillMatches(e))return;let o=!U(a,s,this.settings);await this.cacheTouch(e.path),await i.loadFor(e,this.resolveCardAnchors(s,a.cards),o)}bindScrollSync(){this._scrollDispose&&(this._scrollDispose(),this._scrollDispose=null);let e=this.getActiveView();if(!e)return;let n=e.editor,i=n&&n.cm,a=i?.scrollDOM?i.scrollDOM:e.contentEl.querySelector(".cm-scroller");if(!a)return;let s=Xe(()=>this.handleEditorScroll(e));a.addEventListener("scroll",s,{passive:!0}),this._scrollDispose=()=>{s.cancel(),a.removeEventListener("scroll",s)}}handleEditorScroll(e){let n=this.getParallelView();if(!n||!e.file||n.sourceFile?.path!==e.file.path)return;let i=e.editor,a=i&&i.cm;if(!a?.scrollDOM)return;let s=a.scrollDOM.getBoundingClientRect(),o=Ye(s),l=0;try{let u=a.posAtCoords({x:s.left+20,y:o});u!=null&&(l=a.state.doc.lineAt(u).number-1)}catch{return}let c=-1;for(let u=0;uthis.plugin.t(a,s);t.empty(),new m.Setting(t).setName(e("settingsTitle")).setHeading(),new m.Setting(t).setName(e("settingUiLanguageName")).setDesc(e("settingUiLanguageDesc")).addDropdown(a=>{for(let[s,o]of Object.entries(Ee))a.addOption(s,o);return a.setValue(this.plugin.settings.uiLanguage||b.uiLanguage).onChange(async s=>{this.plugin.settings.uiLanguage=s,await this.plugin.saveSettings(),this.display()})}),new m.Setting(t).setName(e("settingBackendName")).setDesc(e("settingBackendDesc")).addDropdown(a=>a.addOption("api","API / provider").addOption("claude-code","Claude Code CLI").addOption("codex","Codex CLI").setValue(this.plugin.settings.backend).onChange(async s=>{this.plugin.settings.backend=s,s==="api"&&!this.plugin.settings.apiBaseUrl&&Ne(this.plugin.settings,this.plugin.settings.apiProvider||"anthropic"),await this.plugin.saveSettings(),this.display()}));let n=this.plugin.settings.backend==="claude-code"||this.plugin.settings.backend==="codex";if(n&&new m.Setting(t).setName(e("settingCliPathName")).setDesc(e("settingCliPathDesc")).addText(a=>a.setPlaceholder(e("settingCliPathPlaceholder")).setValue(this.plugin.settings.cliPath).onChange(s=>{this.plugin.settings.cliPath=s.trim(),this.plugin.saveSettingsDebounced()})),n||new m.Setting(t).setName(e("apiProviderHeader")).setHeading(),!n){let a=x(this.plugin.settings);new m.Setting(t).setName(e("settingProviderPresetName")).setDesc(e("settingProviderPresetDesc")).addDropdown(s=>{for(let[o,c]of Object.entries(R))s.addOption(o,c.label);return s.setValue(this.plugin.settings.apiProvider).onChange(async o=>{Ne(this.plugin.settings,o),await this.plugin.saveSettings(),this.display()})}),new m.Setting(t).setName(e("settingApiFormatName")).setDesc(e("settingApiFormatDesc")).addDropdown(s=>{for(let[o,c]of Object.entries(S))s.addOption(o,c.label);return s.setValue(P(this.plugin.settings)).onChange(async o=>{this.plugin.settings.apiFormat=o,await this.plugin.saveSettings(),this.display()})}),new m.Setting(t).setName(e("settingBaseUrlName")).setDesc(e("settingBaseUrlDesc")).addText(s=>s.setPlaceholder((this.plugin.settings.apiProvider||"").startsWith("custom-")?"https://your-provider.example/v1":a.baseUrl||S[P(this.plugin.settings)].defaultBaseUrl).setValue(this.plugin.settings.apiBaseUrl).onChange(o=>{this.plugin.settings.apiBaseUrl=o.trim(),this.plugin.saveSettingsDebounced()})),new m.Setting(t).setName(e("settingApiKeyName")).setDesc(e("settingApiKeyDesc")).addText(s=>(s.inputEl.type="password",s.setPlaceholder("sk-...").setValue(this.plugin.settings.apiKey).onChange(o=>{this.plugin.settings.apiKey=o.trim(),this.plugin.saveSettingsDebounced()}))),new m.Setting(t).setName(e("settingApiKeyEnvName")).setDesc(e("settingApiKeyEnvDesc")).addText(s=>s.setPlaceholder(a.envVar||"OPENAI_API_KEY").setValue(this.plugin.settings.apiKeyEnvVar).onChange(o=>{this.plugin.settings.apiKeyEnvVar=o.trim(),this.plugin.saveSettingsDebounced()})),new m.Setting(t).setName(e("settingAuthTypeName")).setDesc(e("settingAuthTypeDesc")).addDropdown(s=>{for(let[o,c]of Object.entries(Fe))s.addOption(o,c);return s.setValue(this.plugin.settings.apiAuthType||"auto").onChange(async o=>{this.plugin.settings.apiAuthType=o,await this.plugin.saveSettings()})}),new m.Setting(t).setName(e("settingHeadersName")).setDesc(e("settingHeadersDesc")).addTextArea(s=>s.setPlaceholder("cf-aig-authorization: Bearer ...").setValue(this.plugin.settings.apiHeaders).onChange(o=>{this.plugin.settings.apiHeaders=o,this.plugin.saveSettingsDebounced()})),new m.Setting(t).setName(e("settingMaxTokensName")).addText(s=>s.setValue(String(this.plugin.settings.apiMaxTokens)).onChange(o=>{let c=parseInt(o,10);!Number.isNaN(c)&&c>0&&(this.plugin.settings.apiMaxTokens=c,this.plugin.saveSettingsDebounced())})),new m.Setting(t).setName(e("settingStreamingName")).setDesc(e("settingStreamingDesc")).addToggle(s=>s.setValue(this.plugin.settings.streaming??!0).onChange(async o=>{this.plugin.settings.streaming=o,await this.plugin.saveSettings()}))}new m.Setting(t).setName(e("settingModelName")).setDesc(e(n?"settingModelDescCli":"settingModelDescApi")).addText(a=>a.setPlaceholder(n?b.model:x(this.plugin.settings).model||"model-id").setValue(this.plugin.settings.model).onChange(s=>{this.plugin.settings.model=s.trim()||(n?b.model:""),this.plugin.saveSettingsDebounced()})),new m.Setting(t).setName(e("settingMaxInputName")).setDesc(e("settingMaxInputDesc")).addText(a=>a.setValue(String(this.plugin.settings.maxDocChars||b.maxDocChars)).onChange(s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>=1e3&&(this.plugin.settings.maxDocChars=o,this.plugin.saveSettingsDebounced())})),new m.Setting(t).setName(e("promptHeader")).setHeading(),new m.Setting(t).setName(e("settingPromptLanguageName")).setDesc(e("settingPromptLanguageDesc")).addDropdown(a=>{for(let[s,o]of Object.entries($))a.addOption(s,o);return a.setValue(this.plugin.settings.promptLanguage||b.promptLanguage).onChange(async s=>{this.plugin.settings.promptLanguage=s,await this.plugin.saveSettings()})}),new m.Setting(t).setName(e("settingCardRangeName")).setDesc(e("settingCardRangeDesc")).addText(a=>a.setPlaceholder("min").setValue(String(this.plugin.settings.minCards||b.minCards)).onChange(s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.minCards=o,this.plugin.settings.maxCardsa.setPlaceholder("max").setValue(String(this.plugin.settings.maxCards||b.maxCards)).onChange(s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.maxCards=Math.max(o,this.plugin.settings.minCards||b.minCards),this.plugin.saveSettingsDebounced())})),new m.Setting(t).setName(e("settingCustomPromptName")).setDesc(e("settingCustomPromptDesc")).addTextArea(a=>(a.inputEl.rows=8,a.setPlaceholder(e("settingCustomPromptPlaceholder")).setValue(this.plugin.settings.customSystemPrompt||"").onChange(s=>{this.plugin.settings.customSystemPrompt=s,this.plugin.saveSettingsDebounced()}))),new m.Setting(t).setName(e("settingTestBackendName")).setDesc(e(n?"settingTestBackendDescCli":"settingTestBackendDescApi")).addButton(a=>a.setButtonText("Test").onClick(async()=>{try{let s=await ar(this.plugin.settings);new m.Notice(`\u2713 ${s.slice(0,180)}`,8e3)}catch(s){new m.Notice(e("backendTestFailed",{error:s.message}),1e4)}})),new m.Setting(t).setName(e("settingExportFolderName")).setDesc(e("settingExportFolderDesc")).addText(a=>a.setValue(this.plugin.settings.exportFolder).onChange(s=>{this.plugin.settings.exportFolder=s.trim()||b.exportFolder,this.plugin.saveSettingsDebounced()})),new m.Setting(t).setName(e("cacheHeader")).setHeading(),new m.Setting(t).setName(e("settingMaxCacheName")).setDesc(e("settingMaxCacheDesc")).addText(a=>{a.setValue(String(this.plugin.settings.maxCacheEntries||j));let s=async()=>{let o=parseInt(a.getValue(),10);if(Number.isFinite(o)&&o>0){this.plugin.settings.maxCacheEntries=o,await this.plugin.saveSettings();let c=await this.plugin.pruneCacheIfNeeded();c.length>0&&new m.Notice(e("cachePruned",{count:c.length})),this.display()}};return a.inputEl.addEventListener("change",()=>{s()}),a.inputEl.addEventListener("keydown",o=>{o.key==="Enter"&&a.inputEl.blur()}),a});let i=Object.keys(this.plugin.cache).length;new m.Setting(t).setName(e("cachedNotesName",{count:i})).setDesc(e("cachedNotesDesc")).addButton(a=>a.setButtonText(e("clearAllCacheButton")).setWarning().onClick(async()=>{let s=Object.keys(this.plugin.cache).length;await this.plugin.cacheClear(),new m.Notice(e("cacheClearedAll",{count:s})),this.display()}))}};var E=require("obsidian");function M(r,t,e,n){let i=r.createEl("button",{cls:"parallel-reader-icon-button",attr:{type:"button","aria-label":e}});return i.title=e,typeof E.setIcon=="function"?(0,E.setIcon)(i,t):i.textContent=e,i.addEventListener("click",a=>{a.preventDefault(),a.stopPropagation(),Promise.resolve(n()).catch(s=>{console.error(s),new E.Notice(`${e} failed: ${s instanceof Error?s.message:String(s)}`)})}),i}function N(r,t,e,n,i){let a=r.createEl("button",{cls:i||"parallel-reader-text-button",attr:{type:"button"}});return t&&typeof E.setIcon=="function"&&(0,E.setIcon)(a,t),a.createSpan({text:e}),a.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),Promise.resolve(n()).catch(o=>{console.error(o),new E.Notice(`${e} failed: ${o instanceof Error?o.message:String(o)}`)})}),a}async function L(r,t){try{await navigator.clipboard.writeText(r),new E.Notice(t)}catch(e){new E.Notice(`Copy failed: ${e instanceof Error?e.message:String(e)}`)}}function rt(r){return String(r||"").split("/").map(t=>t.trim()).filter(t=>!!t&&t!==".."&&t!==".").join("/")}function nt(r){let t=rt(r);if(!t)return[];let e=t.split("/");return e.map((n,i)=>e.slice(0,i+1).join("/"))}async function Pt(r,t){for(let e of nt(t))if(!r.vault.getAbstractFileByPath(e))try{await r.vault.createFolder(e)}catch(n){if(!r.vault.getAbstractFileByPath(e))throw n}}var A=require("obsidian");var St=require("obsidian");var Pe=class extends St.Modal{constructor(t,e,n,i){super(t),this.plugin=e,this.card=n||{},this.onSave=i}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:this.plugin.t("editCardTitle")});let e=this.createLabeledInput(t,this.plugin.t("editCardTitleField"),this.card.title||""),n=this.createLabeledTextarea(t,this.plugin.t("editCardGistField"),this.card.gist||"",3),i=this.createLabeledTextarea(t,this.plugin.t("editCardBulletsField"),(this.card.bullets||[]).join(` +`),8),a=t.createDiv({cls:"parallel-reader-modal-actions"});N(a,null,this.plugin.t("editCardCancel"),()=>this.close(),"parallel-reader-text-button"),N(a,null,this.plugin.t("editCardSave"),async()=>{await this.onSave({title:e.value.trim()||this.card.title||"",gist:n.value.trim(),bullets:i.value.split(/\r?\n/).map(s=>s.trim()).filter(Boolean)}),this.close()},"parallel-reader-text-button")}createLabeledInput(t,e,n){let i=t.createDiv({cls:"parallel-reader-modal-field"});i.createEl("label",{text:e});let a=i.createEl("input",{attr:{type:"text"}});return a.value=n,a}createLabeledTextarea(t,e,n,i){let a=t.createDiv({cls:"parallel-reader-modal-field"});a.createEl("label",{text:e});let s=a.createEl("textarea");return s.rows=i,s.value=n,s}};var O="parallel-reader-view",Se=class extends A.ItemView{constructor(e,n){super(e);this.stale=!1;this.loadingMessage="";this.errorMessage="";this.plugin=n,this.sections=[],this.sourceFile=null,this.cards=[],this.activeIdx=-1}getViewType(){return O}getDisplayText(){return this.plugin.t("displayName")}getIcon(){return"book-open"}onOpen(){let e=this.containerEl.children[1];return e.empty(),e.addClass("parallel-reader-container"),e.setAttr("tabindex","0"),e.addEventListener("keydown",n=>this.handleKeydown(n)),this.renderEmpty(),this.focusSummaryPane(),Promise.resolve()}onClose(){return Promise.resolve()}renderEmpty(){this.sourceFile=null,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage="";let e=this.containerEl.children[1];e.empty();let n=e.createDiv({cls:"parallel-reader-empty"});n.createEl("h3",{text:this.plugin.t("appTitle")}),n.createEl("p",{text:this.plugin.t("emptyOpenNote")}),n.createEl("code",{text:this.plugin.t("commandGenerate")})}focusSummaryPane(){let e=this.containerEl.children[1];return!e||typeof e.focus!="function"?!1:(e.focus({preventScroll:!0}),!0)}loadFor(e,n,i){this.sourceFile=e,this.sections=n,this.stale=!!i,this.loadingMessage="",this.errorMessage="",this.render()}renderLoading(e,n){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage=n||this.plugin.t("loadingDefault"),this.errorMessage="",this.render()}renderStreamingPreview(e,n){this.sourceFile?.path!==e.path&&(this.sourceFile=e);let i=this.containerEl.children[1],a=i.querySelector(".parallel-reader-streaming-preview");if(a){let g=a.querySelector("pre");g&&(g.textContent=n.slice(-2e3));let p=a.querySelector(".parallel-reader-stream-counter");p&&(p.textContent=`${n.length} chars`);return}i.empty();let o=i.createDiv({cls:"parallel-reader-header"}).createDiv({cls:"parallel-reader-header-row"});o.createEl("div",{text:e.basename,cls:"parallel-reader-title"});let c=o.createDiv({cls:"parallel-reader-actions"});M(c,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(e)});let l=i.createDiv({cls:"parallel-reader-state parallel-reader-loading parallel-reader-streaming-preview"});l.createDiv({cls:"parallel-reader-spinner"});let u=l.createEl("div",{cls:"parallel-reader-state-title"});u.createSpan({text:this.plugin.t("loadingGenerating")+" "}),u.createSpan({cls:"parallel-reader-stream-counter",text:`${n.length} chars`});let h=l.createEl("pre",{cls:"parallel-reader-stream-text"});h.textContent=n.slice(-2e3)}renderError(e,n){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage=n||this.plugin.t("errorTitle"),this.render()}renderEmptyWithHint(e){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage="";let n=this.containerEl.children[1];n.empty();let i=n.createDiv({cls:"parallel-reader-empty"});i.createEl("h3",{text:e.basename}),i.createEl("p",{text:this.plugin.t("emptyNoCache")}),i.createEl("code",{text:this.plugin.t("commandGenerate")})}render(){let e=this.containerEl.children[1];e.empty();let i=e.createDiv({cls:"parallel-reader-header"}).createDiv({cls:"parallel-reader-header-row"});i.createEl("div",{text:this.sourceFile?.basename||"",cls:"parallel-reader-title"});let a=i.createDiv({cls:"parallel-reader-actions"});if(this.sourceFile&&(this.plugin.isGeneratingFile(this.sourceFile)?M(a,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(this.sourceFile)}):M(a,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0)),M(a,"copy",this.plugin.t("actionCopyAll"),()=>this.plugin.copyCurrentViewMarkdown()),M(a,"download",this.plugin.t("actionExport"),()=>this.exportToVault())),this.stale){let c=e.createDiv({cls:"parallel-reader-stale-banner"});c.createSpan({text:this.plugin.t("staleBanner")}),N(c,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-stale-button")}if(this.loadingMessage){let c=e.createDiv({cls:"parallel-reader-state parallel-reader-loading"});c.createDiv({cls:"parallel-reader-spinner"}),c.createEl("div",{text:this.loadingMessage,cls:"parallel-reader-state-title"}),c.createEl("div",{text:this.plugin.t("loadingSubtitle"),cls:"parallel-reader-state-subtitle"});return}if(this.errorMessage){let c=e.createDiv({cls:"parallel-reader-state parallel-reader-error"});c.createEl("div",{text:this.plugin.t("errorTitle"),cls:"parallel-reader-state-title"}),c.createEl("div",{text:this.errorMessage,cls:"parallel-reader-state-subtitle"}),N(c,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-text-button");return}let s=e.createDiv({cls:"parallel-reader-cards"});this.cards=[];let o=this.sourceFile?.path||"";this.sections.forEach((c,l)=>{let u=s.createDiv({cls:"parallel-reader-card"});u.dataset.idx=String(l),c.startLine<0&&u.addClass("parallel-reader-card-unanchored");let h=u.createEl("div",{cls:"parallel-reader-card-title"});if(h.createSpan({text:c.title}),c.startLine<0&&h.createEl("span",{text:" \u26A0",cls:"parallel-reader-warn",title:this.plugin.t("anchorMismatch")}),c.gist){let p=u.createEl("div",{cls:"parallel-reader-gist"});A.MarkdownRenderer.render(this.app,c.gist,p,o,this).catch(()=>{p.setText(c.gist)})}let g=c.bullets||[];if(g.length>0){let p=u.createEl("div",{cls:"parallel-reader-bullets-md"}),f=g.map(y=>`- ${y}`).join(` +`);A.MarkdownRenderer.render(this.app,f,p,o,this).catch(()=>{p.setText(f)})}else c.gist||u.createEl("div",{cls:"parallel-reader-empty-li",text:this.plugin.t("emptyCard")});u.addEventListener("click",p=>{let f=window.getSelection();if(f&&f.toString().length>0)return;let y=p.target;y&&y.tagName==="A"||c.startLine>=0&&this.plugin.scrollEditorToLine(c.startLine,this.sourceFile)}),u.addEventListener("contextmenu",p=>{p.preventDefault();let f=new A.Menu;f.addItem(y=>y.setTitle(this.plugin.t("menuCopyMarkdown")).setIcon("copy").onClick(()=>L(Qe(c),this.plugin.t("copiedMarkdown")))),f.addItem(y=>y.setTitle(this.plugin.t("menuCopyPlain")).setIcon("clipboard-copy").onClick(()=>L(At(c),this.plugin.t("copiedPlain")))),c.anchor&&f.addItem(y=>y.setTitle(this.plugin.t("menuCopyAnchor")).setIcon("quote-glyph").onClick(()=>L(c.anchor,this.plugin.t("copiedAnchor")))),f.addSeparator(),c.startLine>=0&&f.addItem(y=>y.setTitle(this.plugin.t("menuJumpSource")).setIcon("arrow-right").onClick(()=>this.plugin.scrollEditorToLine(c.startLine,this.sourceFile))),f.addSeparator(),f.addItem(y=>y.setTitle(this.plugin.t("menuEditCard")).setIcon("pencil").onClick(()=>this.openEditCardModal(l))),f.addItem(y=>y.setTitle(this.plugin.t("menuDeleteCard")).setIcon("trash").onClick(()=>this.deleteCard(l))),f.showAtMouseEvent(p)}),this.cards.push(u)}),this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].addClass("is-active")}setActiveSection(e){e!==this.activeIdx&&(this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].removeClass("is-active"),this.activeIdx=e,e>=0&&this.cards[e]&&(this.cards[e].addClass("is-active"),this.cards[e].scrollIntoView({block:"nearest",behavior:"smooth"})))}moveActiveSection(e){let n=ve(this.activeIdx,this.sections.length,e);return this.setActiveSection(n),this.focusSummaryPane(),n}jumpToActiveSection(){let e=xe(this.sections,this.activeIdx);return e<0||!this.sourceFile?-1:(this.plugin.scrollEditorToLine(e,this.sourceFile),e)}handleKeydown(e){if(e.altKey&&e.key==="ArrowUp"){e.preventDefault(),this.moveActiveSection(-1);return}if(e.altKey&&e.key==="ArrowDown"){e.preventDefault(),this.moveActiveSection(1);return}!e.altKey&&!e.metaKey&&!e.ctrlKey&&!e.shiftKey&&e.key==="Enter"&&this.jumpToActiveSection()>=0&&e.preventDefault()}async deleteCard(e){if(!this.sourceFile)return!1;let n=de(this.sections,e);if(n.length===this.sections.length)return!1;let i=this.sections.length;return this.sections=n,this.activeIdx=pe(e,i,this.activeIdx),await this.plugin.cacheReplaceCards(this.sourceFile.path,n),this.render(),new A.Notice(this.plugin.t("cardDeleted")),!0}openEditCardModal(e){return!this.sourceFile||!this.sections[e]?!1:(new Pe(this.app,this.plugin,this.sections[e],async n=>{await this.updateCard(e,n)}).open(),!0)}async updateCard(e,n){if(!this.sourceFile)return!1;let i=he(this.sections,e,n);return i.length!==this.sections.length?!1:(this.sections=i,await this.plugin.cacheReplaceCards(this.sourceFile.path,i),this.render(),new A.Notice(this.plugin.t("cardSaved")),!0)}async exportToVault(){if(!this.sourceFile)return;let e=rt(this.plugin.settings.exportFolder),n=`${this.sourceFile.basename} - ${this.plugin.t("displayName")}.md`,i=`${e}/${n}`,a=["---",`source: [[${this.sourceFile.basename}]]`,`generated: ${new Date().toISOString().slice(0,10)}`,"tool: parallel-reader","---","",ne(`${this.sourceFile.basename} \xB7 ${this.plugin.t("displayName")}`,this.sections),""].join(` +`),s=this.plugin.app;await Pt(s,e);let o=s.vault.getAbstractFileByPath(i);o instanceof A.TFile?await s.vault.modify(o,a):await s.vault.create(i,a),new A.Notice(this.plugin.t("exported",{path:i}))}};var it=class extends d.Plugin{constructor(){super(...arguments);this.activeBatch=null;this._scrollDispose=null;this._settingsSaveTimer=null}get cache(){return this.cacheManager.cache}t(e,n){return w(this.settings||b,e,n)}async onload(){await this.loadSettings(),this.jobs=new z,this.addRibbonIcon("book-open",this.t("ribbonOpen"),async()=>{let e=this.getActiveView();await this.ensureView()&&e?.file&&await this.syncViewToFile(e.file)}),this.registerView(O,e=>new Se(e,this)),this.addCommand({id:"run",name:this.t("cmdRun"),callback:()=>this.runForActiveFile(!1)}),this.addCommand({id:"regen",name:this.t("cmdRegen"),callback:()=>this.runForActiveFile(!0)}),this.addCommand({id:"open-view",name:this.t("cmdOpenView"),callback:async()=>{let e=this.getActiveView();await this.ensureView()&&e?.file&&this.syncViewToFile(e.file)}}),this.addCommand({id:"export-current",name:this.t("cmdExport"),callback:()=>this.exportCurrentView()}),this.addCommand({id:"copy-current-markdown",name:this.t("cmdCopyMarkdown"),callback:()=>this.copyCurrentViewMarkdown()}),this.addCommand({id:"cancel-current",name:this.t("cmdCancel"),callback:()=>this.cancelActiveGeneration()}),this.addCommand({id:"clear-current",name:this.t("cmdClearCurrent"),callback:async()=>{let e=this.getActiveView();if(!e?.file)return new d.Notice(this.t("noCurrentNote"));await this.cacheManager.delete(e.file.path),new d.Notice(this.t("cacheClearedFile",{name:e.file.basename}))}}),this.addCommand({id:"clear-all",name:this.t("cmdClearAll"),callback:async()=>{let e=Object.keys(this.cacheManager.cache).length;await this.cacheManager.clear(),new d.Notice(this.t("cacheClearedAll",{count:e}))}}),this.addCommand({id:"card-prev",name:this.t("cmdCardPrev"),callback:()=>this.moveActiveCard(-1)}),this.addCommand({id:"card-next",name:this.t("cmdCardNext"),callback:()=>this.moveActiveCard(1)}),this.addCommand({id:"card-jump",name:this.t("cmdCardJump"),callback:()=>this.jumpActiveCard()}),this.addCommand({id:"batch-generate",name:this.t("cmdBatchGenerate"),callback:()=>this.runBatchForFolder()}),this.addSettingTab(new Ae(this.app,this)),this.registerEvent(this.app.workspace.on("active-leaf-change",()=>this.bindScrollSync())),this.registerEvent(this.app.workspace.on("file-open",e=>{e&&this.syncViewToFile(e)})),this.registerEvent(this.app.workspace.on("file-menu",(e,n)=>this.addFileMenuItems(e,n))),this.registerEvent(this.app.vault.on("rename",(e,n)=>{e instanceof d.TFile&&this.handleFileRename(e,n)})),this.registerEvent(this.app.vault.on("delete",e=>{e instanceof d.TFile&&this.handleFileDelete(e)})),this.bindScrollSync()}onunload(){this.flushSettingsSave(),this.flushCacheSave()}async loadSettings(){let n=(await this.loadData()||{}).settings||{};this.settings=Ie(Object.assign({},b,n)),this.cacheManager=new q(this.app.vault.adapter,this.app.vault.configDir,this.manifest?.id||"parallel-reader",()=>this.settings),await this.cacheManager.load()}async saveSettings(){this._settingsSaveTimer&&(clearTimeout(this._settingsSaveTimer),this._settingsSaveTimer=null),await this.saveData({settings:this.settings})}saveSettingsDebounced(e=400){this._settingsSaveTimer&&clearTimeout(this._settingsSaveTimer),this._settingsSaveTimer=setTimeout(()=>{this._settingsSaveTimer=null,this.saveSettings().catch(n=>console.error("[parallel-reader] failed to save settings",n))},e)}async flushSettingsSave(){this._settingsSaveTimer&&(clearTimeout(this._settingsSaveTimer),this._settingsSaveTimer=null,await this.saveSettings())}cacheTouch(e){return this.cacheManager.touch(e)}scheduleCacheSave(e=5e3){this.cacheManager.scheduleSave(e)}async flushCacheSave(){await this.cacheManager.flush()}async cacheReplaceCards(e,n){return this.cacheManager.replaceCards(e,n)}async cacheClear(){return this.cacheManager.clear()}async pruneCacheIfNeeded(){return this.cacheManager.pruneIfNeeded()}async ensureView(){let{workspace:e}=this.app,n=e.getLeavesOfType(O)[0];if(!n){let i=e.getRightLeaf(!1);if(!i)return new d.Notice(this.t("viewOpenFailed")),null;n=i,await n.setViewState({type:O,active:!0})}return await e.revealLeaf(n),n.view}getActiveView(){return this.app.workspace.getActiveViewOfType(d.MarkdownView)}getParallelView(){return this.app.workspace.getLeavesOfType(O)[0]?.view}moveActiveCard(e){let n=this.getParallelView();return n?.sections.length?n.moveActiveSection(e):(new d.Notice(this.t("noActiveCard")),-1)}jumpActiveCard(){let e=this.getParallelView();return!e||e.jumpToActiveSection()<0?(new d.Notice(this.t("noActiveCard")),!1):!0}isGeneratingFile(e){return!!e&&!!e.path&&this.jobs.isRunning(e.path)}cancelGenerationForFile(e){if(!e?.path)return new d.Notice(this.t("noCancelableJob")),!1;let n=this.jobs.get(e.path),i=Ze(this.settings,n),a=this.jobs.cancel(e.path);return a&&this.activeBatch?.currentPath===e.path&&ce(this.activeBatch),new d.Notice(a?this.t(i):this.t("noCancelableJob")),a}requestBatchCancellation(){return ce(this.activeBatch)}viewIsShowingFile(e,n){return!!e&&!!n&&e.sourceFile?.path===n.path}activeFileStillMatches(e){let n=this.getActiveView();return!n?.file||n.file.path===e.path}cancelActiveGeneration(){if(this.requestBatchCancellation()){let i=this.activeBatch?.currentPath;i&&this.jobs.cancel(i),new d.Notice(this.t("batchCancelRequested"));return}let e=this.getActiveView();if(e?.file&&this.cancelGenerationForFile(e.file))return;let n=this.getParallelView();n?.sourceFile?this.cancelGenerationForFile(n.sourceFile):new d.Notice(this.t("noCancelableJob"))}confirmRegenerateEditedCards(){return new Promise(e=>{let n=this.t("confirmRegenerateEditedCards"),i=new d.Modal(this.app);i.titleEl.setText(this.t("displayName")),i.contentEl.createEl("p",{text:n});let a=i.contentEl.createDiv({cls:"modal-button-container"});a.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{i.close(),e(!1)}),a.createEl("button",{text:"OK",cls:"mod-cta"}).addEventListener("click",()=>{i.close(),e(!0)}),i.onClose=()=>e(!1),i.open()})}addFileMenuItems(e,n){!(n instanceof d.TFile)||!n.path.endsWith(".md")||(e.addSeparator(),e.addItem(i=>i.setTitle(this.t("fileMenuGenerate")).setIcon("book-open").onClick(()=>this.runForFile(n,!1))),e.addItem(i=>i.setTitle(this.t("fileMenuRegen")).setIcon("refresh-cw").onClick(()=>this.runForFile(n,!0))),this.cacheManager.get(n.path)&&e.addItem(i=>i.setTitle(this.t("fileMenuClear")).setIcon("trash").onClick(async()=>{await this.cacheManager.delete(n.path),new d.Notice(this.t("cacheClearedFile",{name:n.basename}))})))}async handleFileRename(e,n){if(!(e instanceof d.TFile)||!n)return;let i=n.endsWith(".md"),a=e.path.endsWith(".md");if(!i&&!a)return;if(i&&!a){await this.cacheManager.delete(n);let o=this.getParallelView();o&&o.sourceFile?.path===n&&o.renderEmpty();return}if(!i)return;await this.cacheManager.move(n,e.path);let s=this.getParallelView();s?.sourceFile&&(s.sourceFile.path===n||s.sourceFile.path===e.path)&&(s.sourceFile=e,await this.syncViewToFile(e))}async handleFileDelete(e){if(!(e instanceof d.TFile))return;await this.cacheManager.delete(e.path);let n=this.getParallelView();n?.sourceFile?.path===e.path&&n.renderEmpty()}async exportCurrentView(){let e=this.getActiveView(),n=await this.ensureView();if(n){if((!n.sourceFile||!n.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!n.sourceFile||!n.sections.length){new d.Notice(this.t("noExportContent"));return}await n.exportToVault()}}async copyCurrentViewMarkdown(){let e=this.getActiveView(),n=await this.ensureView();if(n){if((!n.sourceFile||!n.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!n.sourceFile||!n.sections.length){new d.Notice(this.t("noCopyContent"));return}await L(ne(`${n.sourceFile.basename} \xB7 ${this.t("displayName")}`,n.sections),this.t("copiedAllMarkdown"))}}async runForActiveFile(e){let n=this.getActiveView();if(!n?.file){new d.Notice(this.t("openNoteFirst"));return}return this.runForFile(n.file,e)}async runForFile(e,n){if(!e){new d.Notice(this.t("openNoteFirst"));return}if(this.jobs.isRunning(e.path)){new d.Notice(this.t("alreadyGenerating"));return}if(He(this.cacheManager.get(e.path),n)&&!await this.confirmRegenerateEditedCards()){new d.Notice(this.t("regenerateCancelled"));return}let i=null;return this.jobs.start(e.path,async a=>{a.setPhase("reading");let s=await this.app.vault.read(e);if(a.throwIfCancelled(),!s.trim()){new d.Notice(this.t("emptyNote"));return}if(i=await this.ensureView(),!i)return;if(a.throwIfCancelled(),a.setPhase("cache-check"),!n){let p=this.cacheManager.get(e.path);if(p&&U(p,s,this.settings)){await this.cacheTouch(e.path),this.activeFileStillMatches(e)&&await i.loadFor(e,this.resolveCardAnchors(s,p.cards),!1);return}}await i.renderLoading(e,this.t("loadingGenerating"));let o=Number(this.settings.maxDocChars)||b.maxDocChars;s.length>o&&new d.Notice(this.t("longNoteTruncated",{count:o})),new d.Notice(this.t("generatingNotice")),a.setPhase("generating");let c=i,l=c?p=>{!p.done&&this.viewIsShowingFile(c,e)&&c.renderStreamingPreview(e,p.accumulated)}:void 0,u=await Xe(s,this.settings,a,l);if(a.throwIfCancelled(),u.length===0){new d.Notice(this.t("noCardsReturned"));return}let h=u.map(p=>({title:p.title,anchor:p.anchor,gist:p.gist,bullets:p.bullets}));a.setPhase("saving"),await this.cacheManager.put(e.path,s,h,this.settings),a.throwIfCancelled(),this.viewIsShowingFile(i,e)&&await i.loadFor(e,u,!1);let g=u.filter(p=>p.startLine<0).length;new d.Notice(this.t("generationDone",{count:u.length,suffix:g?this.t("unanchoredSuffix",{count:g}):""}))}).catch(async a=>{if(a instanceof G){new d.Notice(a.message);return}if(a instanceof T){i&&this.viewIsShowingFile(i,e)&&await i.renderError(e,this.t("cancelledError")),new d.Notice(this.t("cancelled"));return}let s=$e(a);console.error(a),i&&this.viewIsShowingFile(i,e)&&await i.renderError(e,a.message||String(a)),new d.Notice(this.t("generationFailed",{kind:s==="unknown"?"":` (${s})`,error:a.message||a}))})}async runBatchForFolder(){if(this.activeBatch){new d.Notice(this.t("batchAlreadyRunning"));return}let e=this,n=await new Promise(c=>{class l extends d.Modal{onOpen(){this.contentEl.createEl("p",{text:e.t("batchSelectFolder")}),this.input=this.contentEl.createEl("input",{type:"text"}),this.input.placeholder=e.t("batchFolderPrompt"),this.input.style.width="100%",this.input.addEventListener("keydown",h=>{h.key==="Enter"&&(c(this.input.value.trim()),this.close())}),this.contentEl.createEl("button",{text:"OK"}).addEventListener("click",()=>{c(this.input.value.trim()),this.close()})}onClose(){c(null)}}new l(e.app).open()});if(n===null)return;let i=Le(this.app.vault.getMarkdownFiles(),n);if(i.length===0){new d.Notice(this.t("batchNoMarkdown"));return}let a=Ue();this.activeBatch=a;let s=Be(i.length);try{for(let c=0;c({title:a.title,level:2,anchor:a.anchor,gist:a.gist,startLine:J(e,a.anchor),bullets:a.bullets||[]}));return i.sort((a,s)=>a.startLine<0&&s.startLine<0?0:a.startLine<0?1:s.startLine<0?-1:a.startLine-s.startLine),i}async syncViewToFile(e){if(!e?.path?.endsWith(".md"))return;let n=this.app.workspace.getLeavesOfType(O);if(n.length===0)return;let i=n[0].view;if(!i||!this.activeFileStillMatches(e))return;let a=this.cacheManager.get(e.path);if(!a){await i.loadFor(e,[],!1),i.renderEmptyWithHint(e);return}let s=await this.app.vault.read(e);if(!this.activeFileStillMatches(e))return;let o=!U(a,s,this.settings);await this.cacheTouch(e.path),await i.loadFor(e,this.resolveCardAnchors(s,a.cards),o)}bindScrollSync(){this._scrollDispose&&(this._scrollDispose(),this._scrollDispose=null);let e=this.getActiveView();if(!e)return;let n=e.editor,i=n&&n.cm,a=i?.scrollDOM?i.scrollDOM:e.contentEl.querySelector(".cm-scroller");if(!a)return;let s=tt(()=>this.handleEditorScroll(e));a.addEventListener("scroll",s,{passive:!0}),this._scrollDispose=()=>{s.cancel(),a.removeEventListener("scroll",s)}}handleEditorScroll(e){let n=this.getParallelView();if(!n||!e.file||n.sourceFile?.path!==e.file.path)return;let i=e.editor,a=i&&i.cm;if(!a?.scrollDOM)return;let s=a.scrollDOM.getBoundingClientRect(),o=et(s),c=0;try{let u=a.posAtCoords({x:s.left+20,y:o});u!=null&&(c=a.state.doc.lineAt(u).number-1)}catch{return}let l=-1;for(let u=0;u void) | null = null; _settingsSaveTimer: ReturnType | null = null; @@ -306,10 +312,15 @@ class ParallelReaderPlugin extends Plugin { const job = this.jobs.get(file.path); const noticeKey = cancellationNoticeKey(this.settings, job); const cancelled = this.jobs.cancel(file.path); + if (cancelled && this.activeBatch?.currentPath === file.path) requestBatchCancel(this.activeBatch); new Notice(cancelled ? this.t(noticeKey) : this.t('noCancelableJob')); return cancelled; } + requestBatchCancellation(): boolean { + return requestBatchCancel(this.activeBatch); + } + viewIsShowingFile(view: ParallelReaderView | null, file: TFile) { return !!view && !!file && view.sourceFile?.path === file.path; } @@ -319,6 +330,12 @@ class ParallelReaderPlugin extends Plugin { } cancelActiveGeneration() { + if (this.requestBatchCancellation()) { + const currentPath = this.activeBatch?.currentPath; + if (currentPath) this.jobs.cancel(currentPath); + new Notice(this.t('batchCancelRequested')); + return; + } const active = this.getActiveView(); if (active?.file && this.cancelGenerationForFile(active.file)) return; const view = this.getParallelView(); @@ -537,6 +554,10 @@ class ParallelReaderPlugin extends Plugin { } async runBatchForFolder() { + if (this.activeBatch) { + new Notice(this.t('batchAlreadyRunning')); + return; + } const plugin = this; const folderPath = await new Promise((resolve) => { class FolderPromptModal extends Modal { @@ -569,19 +590,35 @@ class ParallelReaderPlugin extends Plugin { new Notice(this.t('batchNoMarkdown')); return; } + const batch = createBatchRunState(); + this.activeBatch = batch; let stats = createBatchStats(allFiles.length); - for (let i = 0; i < allFiles.length; i++) { - const file = allFiles[i]; - new Notice(this.t('batchProgress', batchProgressVars(i, allFiles.length))); - const content = await this.app.vault.read(file); - const entry = this.cacheManager.get(file.path); - if (shouldSkipBatchFile(entry, content, this.settings)) { - stats = recordBatchSkip(stats); - continue; + try { + for (let i = 0; i < allFiles.length; i++) { + if (batch.cancelled) break; + const file = allFiles[i]; + markBatchFileRunning(batch, file.path); + new Notice(this.t('batchProgress', batchProgressVars(i, allFiles.length))); + const content = await this.app.vault.read(file); + if (batch.cancelled) break; + const entry = this.cacheManager.get(file.path); + if (shouldSkipBatchFile(entry, content, this.settings)) { + stats = recordBatchSkip(stats); + continue; + } + await this.runForFile(file, false); + stats = recordBatchProcessed(stats); } - await this.runForFile(file, false); + } finally { + if (this.activeBatch === batch) this.activeBatch = null; } - new Notice(this.t('batchDone', { total: stats.total, skipped: stats.skipped })); + const batchVars: Record = { + processed: stats.processed, + skipped: stats.skipped, + total: stats.total, + }; + if (batch.cancelled) new Notice(this.t('batchCancelled', batchVars)); + else new Notice(this.t('batchDone', batchVars)); } resolveCardAnchors(content: string, rawCards: RawCard[]) { @@ -728,18 +765,22 @@ export const __test = { classifyGenerationError, copyToClipboard, createRafThrottledHandler, + createBatchRunState, extractJson, findLineForAnchor, folderPathsForTarget, createBatchStats, generationFingerprint, getApiBaseUrl, + markBatchFileRunning, modelForApi, normalizeBatchFolderInput, normalizeCardsPayload, nextCardIndex, pruneCacheEntries, + recordBatchProcessed, recordBatchSkip, + requestBatchCancel, removeCardAt, resolveCliPath, serializeCacheFile, diff --git a/src/batch.ts b/src/batch.ts index 6d732c5..941158c 100644 --- a/src/batch.ts +++ b/src/batch.ts @@ -10,9 +10,15 @@ export interface BatchFileLike { export interface BatchStats { total: number; + processed: number; skipped: number; } +export interface BatchRunState { + cancelled: boolean; + currentPath: string; +} + export function normalizeBatchFolderInput(input: string): string { return (input || '') .trim() @@ -37,13 +43,32 @@ export function batchProgressVars(index: number, total: number): { current: numb } export function createBatchStats(total: number): BatchStats { - return { total, skipped: 0 }; + return { total, processed: 0, skipped: 0 }; +} + +export function recordBatchProcessed(stats: BatchStats): BatchStats { + return { ...stats, processed: stats.processed + 1 }; } export function recordBatchSkip(stats: BatchStats): BatchStats { - return { ...stats, skipped: stats.skipped + 1 }; + return { ...stats, processed: stats.processed + 1, skipped: stats.skipped + 1 }; } export function shouldSkipBatchFile(entry: CacheEntry | null, content: string, settings: PluginSettings): boolean { return !!entry && cacheEntryMatches(entry, content, settings); } + +export function createBatchRunState(): BatchRunState { + return { cancelled: false, currentPath: '' }; +} + +export function markBatchFileRunning(state: BatchRunState, filePath: string): BatchRunState { + state.currentPath = filePath; + return state; +} + +export function requestBatchCancel(state: BatchRunState | null): boolean { + if (!state || state.cancelled) return false; + state.cancelled = true; + return true; +} diff --git a/src/i18n.ts b/src/i18n.ts index 10f4779..efaf462 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -149,9 +149,12 @@ export const STRINGS: Record> = { cmdBatchGenerate: '批量生成对照笔记(当前文件夹)', batchSelectFolder: '请输入要批量处理的文件夹路径(留空为 Vault 根目录)', batchFolderPrompt: '文件夹路径', + batchAlreadyRunning: '批量生成正在运行', + batchCancelRequested: '已请求取消批量生成;当前文件结束后停止', batchNoMarkdown: '该文件夹下没有 Markdown 文件', batchProgress: '批量生成:{current}/{total}', - batchDone: '批量生成完成:共处理 {total} 篇,跳过缓存 {skipped} 篇', + batchDone: '批量生成完成:共处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇', + batchCancelled: '批量生成已取消:已处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇', }, en: { appTitle: 'Parallel Reader', @@ -306,9 +309,12 @@ export const STRINGS: Record> = { cmdBatchGenerate: 'Batch generate parallel notes (current folder)', batchSelectFolder: 'Enter the folder path to batch-process (leave blank for Vault root)', batchFolderPrompt: 'Folder path', + batchAlreadyRunning: 'Batch generation is already running', + batchCancelRequested: 'Batch cancellation requested; it will stop after the current note', batchNoMarkdown: 'No Markdown files found in that folder', batchProgress: 'Batch generating: {current}/{total}', - batchDone: 'Batch complete: processed {total}, skipped {skipped} (cached)', + batchDone: 'Batch complete: processed {processed}/{total}, skipped {skipped} (cached)', + batchCancelled: 'Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached)', }, }; diff --git a/tests/main.test.js b/tests/main.test.js index d8498b2..42eed7f 100644 --- a/tests/main.test.js +++ b/tests/main.test.js @@ -76,6 +76,7 @@ assert.strictEqual(typeof t.getApiBaseUrl, 'function'); assert.strictEqual(typeof t.generationFingerprint, 'function'); assert.strictEqual(typeof t.CacheManager, 'function'); assert.strictEqual(typeof t.GenerationJobManager, 'function'); +assert.strictEqual(typeof t.createBatchRunState, 'function'); assert.strictEqual(typeof t.modelForApi, 'function'); assert.strictEqual(typeof t.activeSectionLine, 'function'); assert.strictEqual(typeof t.touchCacheEntry, 'function'); diff --git a/tests/modules.test.js b/tests/modules.test.js index 01e256f..2f68ad2 100644 --- a/tests/modules.test.js +++ b/tests/modules.test.js @@ -240,8 +240,19 @@ assert.deepStrictEqual( assert.deepStrictEqual(t.batchProgressVars(1, 3), { current: 2, total: 3 }, 'progress vars are one-based'); const batchStats = t.createBatchStats(3); const skippedStats = t.recordBatchSkip(batchStats); -assert.deepStrictEqual(batchStats, { total: 3, skipped: 0 }, 'batch stats are immutable'); -assert.deepStrictEqual(skippedStats, { total: 3, skipped: 1 }, 'batch stats accumulate'); +const processedStats = t.recordBatchProcessed(skippedStats); +assert.deepStrictEqual(batchStats, { total: 3, processed: 0, skipped: 0 }, 'batch stats are immutable'); +assert.deepStrictEqual(skippedStats, { total: 3, processed: 1, skipped: 1 }, 'batch skip updates progress'); +assert.deepStrictEqual(processedStats, { total: 3, processed: 2, skipped: 1 }, 'batch processed count accumulates'); + +const batchState = t.createBatchRunState(); +assert.deepStrictEqual(batchState, { cancelled: false, currentPath: '' }, 'batch state starts idle'); +assert.strictEqual(t.markBatchFileRunning(batchState, 'Reading/a.md'), batchState, 'batch state updates in place'); +assert.strictEqual(batchState.currentPath, 'Reading/a.md', 'batch state records current file'); +assert.strictEqual(t.requestBatchCancel(batchState), true, 'first batch cancellation request succeeds'); +assert.strictEqual(batchState.cancelled, true, 'batch cancellation flag is set'); +assert.strictEqual(t.requestBatchCancel(batchState), false, 'duplicate batch cancellation request is ignored'); +assert.strictEqual(t.requestBatchCancel(null), false, 'missing batch state cannot be cancelled'); // ── i18n.ts ──