diff --git a/main.js b/main.js index 21d10b6..6ed74f7 100644 --- a/main.js +++ b/main.js @@ -1,13 +1,13 @@ 'use strict'; -"use strict";var Jt=Object.create;var le=Object.defineProperty;var Kt=Object.getOwnPropertyDescriptor;var $t=Object.getOwnPropertyNames;var qt=Object.getPrototypeOf,zt=Object.prototype.hasOwnProperty;var Wt=(n,t)=>{for(var e in t)le(n,e,{get:t[e],enumerable:!0})},mt=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of $t(t))!zt.call(n,i)&&i!==e&&le(n,i,{get:()=>t[i],enumerable:!(r=Kt(t,i))||r.enumerable});return n};var ce=(n,t,e)=>(e=n!=null?Jt(qt(n)):{},mt(t||!n||!n.__esModule?le(e,"default",{value:n,enumerable:!0}):e,n)),Yt=n=>mt(le({},"__esModule",{value:!0}),n);var xr={};Wt(xr,{__test:()=>Ar,default:()=>vr});module.exports=Yt(xr);var p=require("obsidian");function G(n,t){if(!t)return-1;let e=u=>u.replace(/\s+/g," ").trim(),r=u=>{let m=[],b=[],w=!1;for(let T=0;T0;continue}w&&(m.push(" "),b.push(T),w=!1),m.push(oe),b.push(T)}return{text:m.join(""),map:b}},i=u=>{if(!u)return-1;let m=n.indexOf(u);if(m===-1)return-1;let b=0;for(let w=0;w=0||(a=i(t.trim()),a>=0))return a;for(let u of[60,40,25,15]){let m=t.trim().slice(0,u);if(a=i(m),a>=0)return a}let s=r(n),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 d=0;for(let u=0;u{for(var e in t)ce(n,e,{get:t[e],enumerable:!0})},gt=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of $t(t))!zt.call(n,i)&&i!==e&&ce(n,i,{get:()=>t[i],enumerable:!(r=Kt(t,i))||r.enumerable});return n};var ue=(n,t,e)=>(e=n!=null?Jt(qt(n)):{},gt(t||!n||!n.__esModule?ce(e,"default",{value:n,enumerable:!0}):e,n)),Yt=n=>gt(ce({},"__esModule",{value:!0}),n);var xr={};Wt(xr,{__test:()=>Ar,default:()=>vr});module.exports=Yt(xr);var p=require("obsidian");function G(n,t){if(!t)return-1;let e=u=>u.replace(/\s+/g," ").trim(),r=u=>{let g=[],b=[],w=!1;for(let T=0;T0;continue}w&&(g.push(" "),b.push(T),w=!1),g.push(le),b.push(T)}return{text:g.join(""),map:b}},i=u=>{if(!u)return-1;let g=n.indexOf(u);if(g===-1)return-1;let b=0;for(let w=0;w=0||(a=i(t.trim()),a>=0))return a;for(let u of[60,40,25,15]){let g=t.trim().slice(0,u);if(a=i(g),a>=0)return a}let s=r(n),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 d=0;for(let u=0;u --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",settingStreamingTimeoutName:"\u6D41\u5F0F\u8D85\u65F6 (ms)",settingStreamingTimeoutDesc:"\u5355\u6B21\u6D41\u5F0F\u8BF7\u6C42\u7684\u8D85\u65F6\u65F6\u95F4\uFF1B\u4F4E\u4E8E 1000ms \u6216\u975E\u6CD5\u503C\u4F1A\u6062\u590D\u9ED8\u8BA4\u503C",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",batchInvalidFolderInput:"\u6587\u4EF6\u5939\u8DEF\u5F84\u65E0\u6548\uFF1A{path}",batchFolderNotFound:"\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A{path}",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\uFF0C\u5931\u8D25 {errors} \u7BC7",batchCancelled:"\u6279\u91CF\u751F\u6210\u5DF2\u53D6\u6D88\uFF1A\u5DF2\u5904\u7406 {processed}/{total} \u7BC7\uFF0C\u8DF3\u8FC7\u7F13\u5B58 {skipped} \u7BC7\uFF0C\u5931\u8D25 {errors} \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",actionCopyError:"Copy error",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.",actionGenerate:"Generate parallel notes",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)",settingStreamingTimeoutName:"Streaming timeout (ms)",settingStreamingTimeoutDesc:"Timeout for one streaming request; invalid values or values below 1000ms use the default",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",batchInvalidFolderInput:"Invalid folder path: {path}",batchFolderNotFound:"Folder not found: {path}",batchNoMarkdown:"No Markdown files found in that folder",batchProgress:"Batch generating: {current}/{total}",batchDone:"Batch complete: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}",batchCancelled:"Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}"}};function Xt(n){let t=n?.uiLanguage;return t==="zh"||t==="en"?t:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function y(n,t,e){let r=Xt(n),i=ue[r]||ue.en,a=ue.en[t]||ue.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 Ie=1e5,Zt=2,j=2,H=100,Qt=1e3,Y={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},De={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},g={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Ie,maxCacheEntries:H,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"}},Le={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"},M={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 U(n){return bt.default.createHash("sha1").update(n,"utf8").digest("hex")}function Ne(n){return Array.isArray(n)?"["+n.map(Ne).join(",")+"]":n&&typeof n=="object"?"{"+Object.keys(n).sort().map(t=>JSON.stringify(t)+":"+Ne(n[t])).join(",")+"}":JSON.stringify(n)}function Oe(n){return n==="api"||n==="anthropic-api"}function v(n){return M[n.apiProvider]||M.anthropic}function A(n){let t=v(n),e=(n.apiFormat||t.format||"").trim();return S[e]?e:t.format}function R(n){let t=A(n),e=v(n),r=(n.apiBaseUrl||"").trim();if(r)return r.replace(/\/+$/,"");if((n.apiProvider||"").startsWith("custom-"))throw new Error(y(n,"errorCustomProviderBaseUrlRequired"));let i=(e.baseUrl||S[t].defaultBaseUrl||"").trim();if(!i)throw new Error(y(n,"errorApiBaseUrlMissing"));return i.replace(/\/+$/,"")}function Be(n){let t=(n.apiAuthType||"auto").trim();if(t&&t!=="auto")return t;let e=v(n),r=A(n);return e.authType||S[r].defaultAuthType||"bearer"}function Ct(n){let t=(n.apiKey||"").trim();if(t)return t;let e=(n.apiKeyEnvVar||v(n).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function N(n){let t=(n.model||"").trim();if(!t)throw new Error(y(n,"errorModelMissing"));let e=v(n),r=[n.apiProvider,e.modelPrefix].map(a=>(a||"").trim()).filter(Boolean),i=t.toLowerCase();for(let a of r){let s=a.toLowerCase();if(i.startsWith(s+"/"))return t.slice(a.length+1).trim()}return t}function _e(n,t){let e=v(n),r=M[t]||M.anthropic,i=(n.model||"").trim(),a=!i||i===g.model||!!e.model&&i===e.model;return{...n,apiProvider:t,apiFormat:r.format,apiBaseUrl:r.baseUrl,apiAuthType:r.authType||"auto",apiKeyEnvVar:r.envVar||"",...a?{model:r.model||""}:{}}}function de(n){let t={...n};De[t.uiLanguage]||(t.uiLanguage=g.uiLanguage),(!t.apiProvider||!M[t.apiProvider])&&(t.apiProvider="anthropic");let e=v(t);(!t.apiFormat||!S[t.apiFormat])&&(t.apiFormat=e.format),(!t.apiAuthType||!Le[t.apiAuthType])&&(t.apiAuthType="auto"),t.backend==="anthropic-api"&&(t.apiProvider=t.apiProvider||"anthropic",t.apiFormat=t.apiFormat||"anthropic-messages",t.apiBaseUrl=t.apiBaseUrl||M.anthropic.baseUrl,t.apiAuthType=t.apiAuthType||"x-api-key",t.apiKeyEnvVar=t.apiKeyEnvVar||"ANTHROPIC_API_KEY");let r=Number(t.apiMaxTokens);(!Number.isFinite(r)||r<=0)&&(t.apiMaxTokens=g.apiMaxTokens);let i=Number(t.maxDocChars);return(!Number.isFinite(i)||i<1e3)&&(t.maxDocChars=g.maxDocChars),t.maxCacheEntries=wt(t.maxCacheEntries),Y[t.promptLanguage]||(t.promptLanguage=g.promptLanguage),t.minCards=ft(t.minCards,g.minCards),t.maxCards=ft(t.maxCards,g.maxCards),t.maxCardsyt(s)-yt(l)||a.localeCompare(o)).slice(0,r.length-e).map(([a])=>a);for(let a of i)delete n[a];return i}function Z(n){let t=de(Object.assign({},g,n||{})),e=Oe(t.backend),r=v(t),i=A(t),a=e?(t.apiBaseUrl||r.baseUrl||S[i]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return U(Ne({cacheSchemaVersion:j,promptVersion:Zt,maxDocChars:Number(t.maxDocChars)||g.maxDocChars,promptLanguage:t.promptLanguage,minCards:t.minCards,maxCards:t.maxCards,customSystemPromptHash:U(t.customSystemPrompt||""),backend:t.backend,model:t.model,apiProvider:e?t.apiProvider:"",apiFormat:e?i:"",apiBaseUrl:a,apiAuthType:e?Be(t):"",apiHeadersHash:e?U(t.apiHeaders||""):"",apiMaxTokens:e?Number(t.apiMaxTokens)||g.apiMaxTokens:0,structuredOutputVersion:1}))}function J(n,t,e){return!!n&&n.schemaVersion===j&&n.contentHash===U(t)&&n.settingsHash===Z(e)}function he(n){return(n||"").trim().split("/").map(t=>t.trim()).filter(t=>t&&t!=="."&&t!=="..").join("/")}function Ve(n){return(n||"").split("/").map(t=>t.trim()).some(t=>t==="."||t==="..")}function Ge(n,t){let e=he(n);return Ve(n)?{valid:!1,reason:"unsafe",folderPath:e}:e&&!t(e)?{valid:!1,reason:"missing",folderPath:e}:{valid:!0,reason:"ok",folderPath:e}}function er(n,t){return t?n.parent?.path===t:!n.path.includes("/")}function Ue(n,t){let e=he(t);return n.filter(r=>er(r,e))}function je(n,t){return{current:n+1,total:t}}function He(n){return{total:n,processed:0,skipped:0,errors:0}}function Je(n){return{...n,processed:n.processed+1}}function Ke(n){return{...n,processed:n.processed+1,skipped:n.skipped+1}}function $e(n){return{...n,processed:n.processed+1,errors:n.errors+1}}function qe(n,t,e){return!!n&&J(n,t,e)}function ze(){return{cancelled:!1,currentPath:""}}function We(n,t){return n.currentPath=t,n}function ge(n){return!n||n.cancelled?!1:(n.cancelled=!0,!0)}function At(n,t,e){return new Promise(r=>{let i=new vt.Modal(n);i.onOpen=()=>{i.contentEl.createEl("p",{text:t});let s=i.contentEl.createDiv({cls:"parallel-reader-modal-field"}).createEl("input",{type:"text"});s.placeholder=e,s.addEventListener("keydown",o=>{o.key==="Enter"&&(r(s.value.trim()),i.close())}),i.contentEl.createEl("button",{text:"OK"}).addEventListener("click",()=>{r(s.value.trim()),i.close()})},i.onClose=()=>r(null),i.open()})}function me(n,t){return n?{...n,lastAccessedAt:t||new Date().toISOString()}:null}function fe(n){return JSON.stringify({version:1,entries:n||{}})}function Ye(n,t){return!!t&&!!n&&typeof n.updatedAt=="string"&&n.updatedAt.trim().length>0}var Q=class{constructor(t,e,r,i){this.adapter=t;this.configDir=e;this.pluginId=r;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(),fe(this.cache))}async load(){this.cache=await this.readFile(),this.prune().length>0&&await this.writeFile()}prune(){let t=this.getSettings();return pe(this.cache,t?.maxCacheEntries||H)}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}touch(t){let e=me(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 r=this.cache[t];if(!r||Object.hasOwn(this.cache,e))return!1;let i={...this.cache,[e]:r};return delete i[t],this.cache=i,await this.save(),!0}async put(t,e,r,i){let a=new Date().toISOString();this.cache[t]={schemaVersion:j,contentHash:U(e),settingsHash:Z(i||this.getSettings()),cards:r,generatedAt:a,lastAccessedAt:a},await this.save()}async replaceCards(t,e){let r=this.cache[t];if(!r)return!1;let i=new Date().toISOString();return this.cache[t]={...r,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 ye(n,t){let e=Array.isArray(n)?n.slice():[];return!Number.isInteger(t)||t<0||t>=e.length||e.splice(t,1),e}function be(n,t,e){if(!Number.isInteger(n)||!Number.isInteger(t)||t<=0||!Number.isInteger(e)||e<0||n<0||n>=t)return e;let r=t-1;return r<=0?-1:n=r.length||(r[t]=Object.assign({},r[t],e||{})),r}function Xe(n,t){let e=(t||[]).map(r=>({title:r.title,level:2,anchor:r.anchor,gist:r.gist,startLine:G(n,r.anchor),bullets:r.bullets||[]}));return e.sort((r,i)=>r.startLine<0&&i.startLine<0?0:r.startLine<0?1:i.startLine<0?-1:r.startLine-i.startLine),e}var Tt=require("child_process"),kt=ce(require("fs")),ve=ce(require("os")),C=ce(require("path"));var K=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}},k=class extends Error{constructor(t){super("\u751F\u6210\u5DF2\u53D6\u6D88"),this.name="GenerationJobCancelledError",this.code="cancelled",this.key=t}},Ze=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(e){console.warn("[parallel-reader] cancel handler error",e)}return!0}throwIfCancelled(){if(this.cancelled)throw new k(this.key)}},ee=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 K(t);let r=new Ze(t);this.jobs.set(t,r);try{r.setPhase("running");let i=await e(r);return r.throwIfCancelled(),i}catch(i){throw r.cancelled&&!(i instanceof k)?new k(t):i}finally{this.jobs.delete(t)}}cancel(t){let e=this.jobs.get(t);return e?e.cancel():!1}};function Qe(n){if(n instanceof k)return"cancelled";let t=n;if(t?.code==="cancelled")return"cancelled";let e=String(t?.message||n);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 te="record_parallel_reader_cards";function we(n){let t=[],e=-1,r=0,i=!1,a=!1;for(let s=0;s0&&r--,r===0&&e>=0&&(t.push(n.slice(e,s+1)),e=-1))}return t}function et(n){let t=(n||"").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 r=we(t);r.sort((i,a)=>a.length-i.length);for(let i of r)try{return JSON.parse(i),i}catch{}return t}function tt(n){let t=n.match(/\{\s*"cards"\s*:\s*\[/);if(!t||t.index===void 0)return null;let e=t.index+t[0].length,r=we(n.slice(e)),i=[];for(let a of r)try{JSON.parse(a),i.push(a)}catch{}return i.length===0?null:'{"cards":['+i.join(",")+"]}"}function P(n,t){let e=et(n),r;try{r=JSON.parse(e)}catch{let a=tt(n);if(a)try{return r=JSON.parse(a),console.warn("[parallel-reader] LLM output was truncated; salvaged",r.cards?.length??0,"complete cards. Consider increasing max tokens."),$(r)}catch{}throw console.warn("[parallel-reader] LLM returned non-JSON. Raw response:",n),new Error(y(t||null,"errorLlmNonJson",{excerpt:(n||"").slice(0,500)}))}return $(r)}function $(n){let t=n;return(t&&Array.isArray(t.cards)?t.cards:[]).filter(r=>!!r&&typeof r=="object").map(r=>({title:typeof r.title=="string"?r.title:"(\u65E0\u6807\u9898)",anchor:typeof r.anchor=="string"?r.anchor:"",gist:typeof r.gist=="string"?r.gist:"",bullets:Array.isArray(r.bullets)?r.bullets.filter(i=>typeof i=="string"):[]}))}function re(n){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 n&&(t.additionalProperties=!1,e.additionalProperties=!1),e}function xt(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:re(!0)}}}function St(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:re(!0)}}}function Pt(){return{name:te,description:"Return the generated Parallel Reader summary cards.",input_schema:re(!0)}}function L(n,t){if(t?.trim())return t.trim();let e=ve.default.homedir(),r=process.platform==="win32",i=r?[".cmd",".exe",""]:[""],a=r?[C.default.join(e,"AppData","Roaming","npm"),C.default.join(e,"AppData","Local","Programs","claude-code"),C.default.join(e,"AppData","Local","Programs","codex"),C.default.join(e,".claude","local"),C.default.join(e,".codex","bin"),C.default.join(e,".bun","bin"),C.default.join(e,"scoop","shims"),"C:\\Program Files\\nodejs"]:[C.default.join(e,"bin"),C.default.join(e,".local","bin"),C.default.join(e,".claude","local"),C.default.join(e,".codex","bin"),C.default.join(e,".bun","bin"),C.default.join(e,".npm-global","bin"),C.default.join(e,".cargo","bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let s of a)for(let o of i){let l=C.default.join(s,n+o);try{if(kt.default.existsSync(l))return l}catch{}}return n}function O(n,t,e,r,i,a=Tt.spawn){return new Promise((s,o)=>{let l,c=!1,d=null,u=f=>{c||(c=!0,d&&clearTimeout(d),o(f))},m=f=>{c||(c=!0,d&&clearTimeout(d),s(f))};try{l=a(n,t,{stdio:["pipe","pipe","pipe"],env:{...process.env,PATH:[process.env.PATH||"","/usr/local/bin","/opt/homebrew/bin",C.default.join(ve.default.homedir(),".local/bin"),C.default.join(ve.default.homedir(),".claude/local")].filter(Boolean).join(":")}})}catch(f){return o(new Error(`Failed to start ${n}: ${f instanceof Error?f.message:String(f)}`))}let b="",w="";if(d=setTimeout(()=>{try{l.kill("SIGKILL")}catch(f){console.warn("[parallel-reader] failed to kill timed-out CLI process",f)}u(new Error(`CLI timed out (${r}ms)`))},r),i&&i.onCancel(()=>{try{l.kill("SIGKILL")}catch(f){console.warn("[parallel-reader] failed to kill cancelled CLI process",f)}u(new k(i.key))}),!l.stdout||!l.stderr||!l.stdin){u(new Error("CLI process streams are unavailable"));return}let T=l.stdout,oe=l.stderr,Me=l.stdin;if(T.on("data",f=>{b+=f.toString("utf8")}),oe.on("data",f=>{w+=f.toString("utf8")}),l.on("error",f=>{u(new Error(`CLI startup error: ${f.message}. Try setting an absolute CLI path.`))}),l.on("close",f=>{if(!c){if(f!==0)return u(new Error(`CLI exited with code ${f} +{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)",settingStreamingTimeoutName:"Streaming timeout (ms)",settingStreamingTimeoutDesc:"Timeout for one streaming request; invalid values or values below 1000ms use the default",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",batchInvalidFolderInput:"Invalid folder path: {path}",batchFolderNotFound:"Folder not found: {path}",batchNoMarkdown:"No Markdown files found in that folder",batchProgress:"Batch generating: {current}/{total}",batchDone:"Batch complete: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}",batchCancelled:"Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}"}};function Xt(n){let t=n?.uiLanguage;return t==="zh"||t==="en"?t:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function y(n,t,e){let r=Xt(n),i=U[r]||U.en,a=U.en[t]||U.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 Ie=1e5,Zt=2,H=2,J=100,Qt=1e3,X={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},De={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},m={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Ie,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"}},Le={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"},M={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 j(n){return bt.default.createHash("sha1").update(n,"utf8").digest("hex")}function Ne(n){return Array.isArray(n)?"["+n.map(Ne).join(",")+"]":n&&typeof n=="object"?"{"+Object.keys(n).sort().map(t=>JSON.stringify(t)+":"+Ne(n[t])).join(",")+"}":JSON.stringify(n)}function Oe(n){return n==="api"||n==="anthropic-api"}function v(n){return M[n.apiProvider]||M.anthropic}function A(n){let t=v(n),e=(n.apiFormat||t.format||"").trim();return S[e]?e:t.format}function R(n){let t=A(n),e=v(n),r=(n.apiBaseUrl||"").trim();if(r)return r.replace(/\/+$/,"");if((n.apiProvider||"").startsWith("custom-"))throw new Error(y(n,"errorCustomProviderBaseUrlRequired"));let i=(e.baseUrl||S[t].defaultBaseUrl||"").trim();if(!i)throw new Error(y(n,"errorApiBaseUrlMissing"));return i.replace(/\/+$/,"")}function Be(n){let t=(n.apiAuthType||"auto").trim();if(t&&t!=="auto")return t;let e=v(n),r=A(n);return e.authType||S[r].defaultAuthType||"bearer"}function Ct(n){let t=(n.apiKey||"").trim();if(t)return t;let e=(n.apiKeyEnvVar||v(n).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function N(n){let t=(n.model||"").trim();if(!t)throw new Error(y(n,"errorModelMissing"));let e=v(n),r=[n.apiProvider,e.modelPrefix].map(a=>(a||"").trim()).filter(Boolean),i=t.toLowerCase();for(let a of r){let s=a.toLowerCase();if(i.startsWith(s+"/"))return t.slice(a.length+1).trim()}return t}function _e(n,t){let e=v(n),r=M[t]||M.anthropic,i=(n.model||"").trim(),a=!i||i===m.model||!!e.model&&i===e.model;return{...n,apiProvider:t,apiFormat:r.format,apiBaseUrl:r.baseUrl,apiAuthType:r.authType||"auto",apiKeyEnvVar:r.envVar||"",...a?{model:r.model||""}:{}}}function de(n){let t={...n};De[t.uiLanguage]||(t.uiLanguage=m.uiLanguage),(!t.apiProvider||!M[t.apiProvider])&&(t.apiProvider="anthropic");let e=v(t);(!t.apiFormat||!S[t.apiFormat])&&(t.apiFormat=e.format),(!t.apiAuthType||!Le[t.apiAuthType])&&(t.apiAuthType="auto"),t.backend==="anthropic-api"&&(t.apiProvider=t.apiProvider||"anthropic",t.apiFormat=t.apiFormat||"anthropic-messages",t.apiBaseUrl=t.apiBaseUrl||M.anthropic.baseUrl,t.apiAuthType=t.apiAuthType||"x-api-key",t.apiKeyEnvVar=t.apiKeyEnvVar||"ANTHROPIC_API_KEY");let r=Number(t.apiMaxTokens);(!Number.isFinite(r)||r<=0)&&(t.apiMaxTokens=m.apiMaxTokens);let i=Number(t.maxDocChars);return(!Number.isFinite(i)||i<1e3)&&(t.maxDocChars=m.maxDocChars),t.maxCacheEntries=wt(t.maxCacheEntries),X[t.promptLanguage]||(t.promptLanguage=m.promptLanguage),t.minCards=ft(t.minCards,m.minCards),t.maxCards=ft(t.maxCards,m.maxCards),t.maxCardsyt(s)-yt(l)||a.localeCompare(o)).slice(0,r.length-e).map(([a])=>a);for(let a of i)delete n[a];return i}function Q(n){let t=de(Object.assign({},m,n||{})),e=Oe(t.backend),r=v(t),i=A(t),a=e?(t.apiBaseUrl||r.baseUrl||S[i]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return j(Ne({cacheSchemaVersion:H,promptVersion:Zt,maxDocChars:Number(t.maxDocChars)||m.maxDocChars,promptLanguage:t.promptLanguage,minCards:t.minCards,maxCards:t.maxCards,customSystemPromptHash:j(t.customSystemPrompt||""),backend:t.backend,model:t.model,apiProvider:e?t.apiProvider:"",apiFormat:e?i:"",apiBaseUrl:a,apiAuthType:e?Be(t):"",apiHeadersHash:e?j(t.apiHeaders||""):"",apiMaxTokens:e?Number(t.apiMaxTokens)||m.apiMaxTokens:0,structuredOutputVersion:1}))}function K(n,t,e){return!!n&&n.schemaVersion===H&&n.contentHash===j(t)&&n.settingsHash===Q(e)}function he(n){return(n||"").trim().split("/").map(t=>t.trim()).filter(t=>t&&t!=="."&&t!=="..").join("/")}function Ve(n){return(n||"").split("/").map(t=>t.trim()).some(t=>t==="."||t==="..")}function Ge(n,t){let e=he(n);return Ve(n)?{valid:!1,reason:"unsafe",folderPath:e}:e&&!t(e)?{valid:!1,reason:"missing",folderPath:e}:{valid:!0,reason:"ok",folderPath:e}}function er(n,t){return t?n.parent?.path===t:!n.path.includes("/")}function Ue(n,t){let e=he(t);return n.filter(r=>er(r,e))}function je(n,t){return{current:n+1,total:t}}function He(n){return{total:n,processed:0,skipped:0,errors:0}}function Je(n){return{...n,processed:n.processed+1}}function Ke(n){return{...n,processed:n.processed+1,skipped:n.skipped+1}}function $e(n){return{...n,processed:n.processed+1,errors:n.errors+1}}function qe(n,t,e){return!!n&&K(n,t,e)}function ze(){return{cancelled:!1,currentPath:""}}function We(n,t){return n.currentPath=t,n}function me(n){return!n||n.cancelled?!1:(n.cancelled=!0,!0)}function At(n,t,e){return new Promise(r=>{let i=new vt.Modal(n);i.onOpen=()=>{i.contentEl.createEl("p",{text:t});let s=i.contentEl.createDiv({cls:"parallel-reader-modal-field"}).createEl("input",{type:"text"});s.placeholder=e,s.addEventListener("keydown",o=>{o.key==="Enter"&&(r(s.value.trim()),i.close())}),i.contentEl.createEl("button",{text:"OK"}).addEventListener("click",()=>{r(s.value.trim()),i.close()})},i.onClose=()=>r(null),i.open()})}function ge(n,t){return n?{...n,lastAccessedAt:t||new Date().toISOString()}:null}function fe(n){return JSON.stringify({version:1,entries:n||{}})}function Ye(n,t){return!!t&&!!n&&typeof n.updatedAt=="string"&&n.updatedAt.trim().length>0}var ee=class{constructor(t,e,r,i){this.adapter=t;this.configDir=e;this.pluginId=r;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(),fe(this.cache))}async load(){this.cache=await this.readFile(),this.prune().length>0&&await this.writeFile()}prune(){let t=this.getSettings();return pe(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}touch(t){let e=ge(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 r=this.cache[t];if(!r||Object.hasOwn(this.cache,e))return!1;let i={...this.cache,[e]:r};return delete i[t],this.cache=i,await this.save(),!0}async put(t,e,r,i){let a=new Date().toISOString();this.cache[t]={schemaVersion:H,contentHash:j(e),settingsHash:Q(i||this.getSettings()),cards:r,generatedAt:a,lastAccessedAt:a},await this.save()}async replaceCards(t,e){let r=this.cache[t];if(!r)return!1;let i=new Date().toISOString();return this.cache[t]={...r,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 ye(n,t){let e=Array.isArray(n)?n.slice():[];return!Number.isInteger(t)||t<0||t>=e.length||e.splice(t,1),e}function be(n,t,e){if(!Number.isInteger(n)||!Number.isInteger(t)||t<=0||!Number.isInteger(e)||e<0||n<0||n>=t)return e;let r=t-1;return r<=0?-1:n=r.length||(r[t]=Object.assign({},r[t],e||{})),r}function Xe(n,t){let e=(t||[]).map(r=>({title:r.title,level:2,anchor:r.anchor,gist:r.gist,startLine:G(n,r.anchor),bullets:r.bullets||[]}));return e.sort((r,i)=>r.startLine<0&&i.startLine<0?0:r.startLine<0?1:i.startLine<0?-1:r.startLine-i.startLine),e}var Tt=require("child_process"),kt=ue(require("fs")),ve=ue(require("os")),C=ue(require("path"));var $=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}},k=class extends Error{constructor(t){super("\u751F\u6210\u5DF2\u53D6\u6D88"),this.name="GenerationJobCancelledError",this.code="cancelled",this.key=t}},Ze=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(e){console.warn("[parallel-reader] cancel handler error",e)}return!0}throwIfCancelled(){if(this.cancelled)throw new k(this.key)}},te=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 $(t);let r=new Ze(t);this.jobs.set(t,r);try{r.setPhase("running");let i=await e(r);return r.throwIfCancelled(),i}catch(i){throw r.cancelled&&!(i instanceof k)?new k(t):i}finally{this.jobs.delete(t)}}cancel(t){let e=this.jobs.get(t);return e?e.cancel():!1}};function Qe(n){if(n instanceof k)return"cancelled";let t=n;if(t?.code==="cancelled")return"cancelled";let e=String(t?.message||n);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 re="record_parallel_reader_cards";function we(n){let t=[],e=-1,r=0,i=!1,a=!1;for(let s=0;s0&&r--,r===0&&e>=0&&(t.push(n.slice(e,s+1)),e=-1))}return t}function et(n){let t=(n||"").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 r=we(t);r.sort((i,a)=>a.length-i.length);for(let i of r)try{return JSON.parse(i),i}catch{}return t}function tt(n){let t=n.match(/\{\s*"cards"\s*:\s*\[/);if(!t||t.index===void 0)return null;let e=t.index+t[0].length,r=we(n.slice(e)),i=[];for(let a of r)try{JSON.parse(a),i.push(a)}catch{}return i.length===0?null:'{"cards":['+i.join(",")+"]}"}function P(n,t){let e=et(n),r;try{r=JSON.parse(e)}catch{let a=tt(n);if(a)try{return r=JSON.parse(a),console.warn("[parallel-reader] LLM output was truncated; salvaged",r.cards?.length??0,"complete cards. Consider increasing max tokens."),q(r)}catch{}throw console.warn("[parallel-reader] LLM returned non-JSON. Raw response:",n),new Error(y(t||null,"errorLlmNonJson",{excerpt:(n||"").slice(0,500)}))}return q(r)}function q(n){let t=n;return(t&&Array.isArray(t.cards)?t.cards:[]).filter(r=>!!r&&typeof r=="object").map(r=>({title:typeof r.title=="string"?r.title:"(\u65E0\u6807\u9898)",anchor:typeof r.anchor=="string"?r.anchor:"",gist:typeof r.gist=="string"?r.gist:"",bullets:Array.isArray(r.bullets)?r.bullets.filter(i=>typeof i=="string"):[]}))}function ne(n){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 n&&(t.additionalProperties=!1,e.additionalProperties=!1),e}function xt(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:ne(!0)}}}function St(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:ne(!0)}}}function Pt(){return{name:re,description:"Return the generated Parallel Reader summary cards.",input_schema:ne(!0)}}function L(n,t){if(t?.trim())return t.trim();let e=ve.default.homedir(),r=process.platform==="win32",i=r?[".cmd",".exe",""]:[""],a=r?[C.default.join(e,"AppData","Roaming","npm"),C.default.join(e,"AppData","Local","Programs","claude-code"),C.default.join(e,"AppData","Local","Programs","codex"),C.default.join(e,".claude","local"),C.default.join(e,".codex","bin"),C.default.join(e,".bun","bin"),C.default.join(e,"scoop","shims"),"C:\\Program Files\\nodejs"]:[C.default.join(e,"bin"),C.default.join(e,".local","bin"),C.default.join(e,".claude","local"),C.default.join(e,".codex","bin"),C.default.join(e,".bun","bin"),C.default.join(e,".npm-global","bin"),C.default.join(e,".cargo","bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let s of a)for(let o of i){let l=C.default.join(s,n+o);try{if(kt.default.existsSync(l))return l}catch{}}return n}function O(n,t,e,r,i,a=Tt.spawn){return new Promise((s,o)=>{let l,c=!1,d=null,u=f=>{c||(c=!0,d&&clearTimeout(d),o(f))},g=f=>{c||(c=!0,d&&clearTimeout(d),s(f))};try{l=a(n,t,{stdio:["pipe","pipe","pipe"],env:{...process.env,PATH:[process.env.PATH||"","/usr/local/bin","/opt/homebrew/bin",C.default.join(ve.default.homedir(),".local/bin"),C.default.join(ve.default.homedir(),".claude/local")].filter(Boolean).join(":")}})}catch(f){return o(new Error(`Failed to start ${n}: ${f instanceof Error?f.message:String(f)}`))}let b="",w="";if(d=setTimeout(()=>{try{l.kill("SIGKILL")}catch(f){console.warn("[parallel-reader] failed to kill timed-out CLI process",f)}u(new Error(`CLI timed out (${r}ms)`))},r),i&&i.onCancel(()=>{try{l.kill("SIGKILL")}catch(f){console.warn("[parallel-reader] failed to kill cancelled CLI process",f)}u(new k(i.key))}),!l.stdout||!l.stderr||!l.stdin){u(new Error("CLI process streams are unavailable"));return}let T=l.stdout,le=l.stderr,Me=l.stdin;if(T.on("data",f=>{b+=f.toString("utf8")}),le.on("data",f=>{w+=f.toString("utf8")}),l.on("error",f=>{u(new Error(`CLI startup error: ${f.message}. Try setting an absolute CLI path.`))}),l.on("close",f=>{if(!c){if(f!==0)return u(new Error(`CLI exited with code ${f} stderr: -${w.slice(0,1e3)}`));m({stdout:b,stderr:w})}}),e)try{Me.write(e),Me.end()}catch{}else try{Me.end()}catch(f){console.warn("[parallel-reader] failed to close CLI stdin",f)}})}async function Ft(n,t,e,r){let i=L("claude",e.cliPath),a=Number(e.apiMaxTokens)||4096,s=["-p","--output-format","json","--max-tokens",String(a),"--append-system-prompt",n,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"],{stdout:o}=await O(i,s,t,e.cliTimeoutMs,r),l="";try{let c=JSON.parse(o);if(Array.isArray(c)){let d=c.find(u=>u.type==="result");d&&typeof d.result=="string"&&(l=d.result)}else c&&typeof c=="object"&&(l=c.result||c.content||"")}catch{throw new Error(`claude CLI returned unexpected output: +${w.slice(0,1e3)}`));g({stdout:b,stderr:w})}}),e)try{Me.write(e),Me.end()}catch{}else try{Me.end()}catch(f){console.warn("[parallel-reader] failed to close CLI stdin",f)}})}async function Ft(n,t,e,r){let i=L("claude",e.cliPath),a=Number(e.apiMaxTokens)||4096,s=["-p","--output-format","json","--max-tokens",String(a),"--append-system-prompt",n,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"],{stdout:o}=await O(i,s,t,e.cliTimeoutMs,r),l="";try{let c=JSON.parse(o);if(Array.isArray(c)){let d=c.find(u=>u.type==="result");d&&typeof d.result=="string"&&(l=d.result)}else c&&typeof c=="object"&&(l=c.result||c.content||"")}catch{throw new Error(`claude CLI returned unexpected output: `+o.slice(0,500))}if(!l)throw console.warn("[parallel-reader] claude CLI returned no result. Full stdout:",o),new Error(`claude CLI returned no result. Output: `+o.slice(0,500));return P(l,e)}async function Et(n,t,e,r){let i=L("codex",e.cliPath),a=`<> ${n} @@ -78,17 +78,17 @@ ${a}`}function ar(n,t,e,r,i){return n==="en"?`Non-overridable output contract: - ${r} - 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 Ae(n,t){let e=Number(t.maxDocChars)||Ie,r=Y[t.promptLanguage]?t.promptLanguage:g.promptLanguage,i=Math.max(1,Number(t.minCards)||g.minCards),a=Math.max(i,Number(t.maxCards)||g.maxCards),s=tr(r),o=n.length>e?n.slice(0,e)+(r==="en"?` +- JSON shape: ${i}`}function Ae(n,t){let e=Number(t.maxDocChars)||Ie,r=X[t.promptLanguage]?t.promptLanguage:m.promptLanguage,i=Math.max(1,Number(t.minCards)||m.minCards),a=Math.max(i,Number(t.maxCards)||m.maxCards),s=tr(r),o=n.length>e?n.slice(0,e)+(r==="en"?` [Document truncated]`:` -[\u6587\u6863\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD]`):n,l='{"cards":[{"title":"...","anchor":"...","gist":"...","bullets":["...","..."]}]}',c=rr(r),d={minCards:i,maxCards:a,languageInstruction:s,schema:l,example:c},u=nr(t.customSystemPrompt,d).trim(),m=ar(r,i,a,s,l),b=ir(r,i,a,s,l,c),w=u?`${u} +[\u6587\u6863\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD]`):n,l='{"cards":[{"title":"...","anchor":"...","gist":"...","bullets":["...","..."]}]}',c=rr(r),d={minCards:i,maxCards:a,languageInstruction:s,schema:l,example:c},u=nr(t.customSystemPrompt,d).trim(),g=ar(r,i,a,s,l),b=ir(r,i,a,s,l,c),w=u?`${u} -${m}`:b,T=r==="en"?`Source document: +${g}`:b,T=r==="en"?`Source document: ${o}`:`\u4EE5\u4E0B\u662F\u9700\u8981\u5904\u7406\u7684\u6587\u6863\u5168\u6587\uFF1A -${o}`;return{system:w,user:T}}function xe(n){let t=v(n),e=S[A(n)];return t.tokenLimitField||e?.tokenLimitField||"max_tokens"}function B(n,t,e,r){let i=!r||r.structured!==!1,a={model:N(e),max_tokens:Number(e.apiMaxTokens)||4096,system:n,messages:[{role:"user",content:t}]};return i&&(a.tools=[Pt()],a.tool_choice={type:"tool",name:te}),a}function _(n,t,e,r){let i=!r||r.structured!==!1,a={model:N(e),messages:[{role:"system",content:n},{role:"user",content:t}]};return a[xe(e)]=Number(e.apiMaxTokens)||4096,i&&(a.response_format=xt()),a}function q(n,t,e,r){let i=!r||r.structured!==!1,a={model:N(e),instructions:n,input:t,max_output_tokens:Number(e.apiMaxTokens)||4096};return i&&(a.text=St()),a}function z(n,t,e,r){let i=!r||r.structured!==!1,a={temperature:0,maxOutputTokens:Number(e.apiMaxTokens)||4096};return i&&(a.responseMimeType="application/json",a.responseJsonSchema=re(!1)),{systemInstruction:{parts:[{text:n}]},contents:[{role:"user",parts:[{text:t}]}],generationConfig:a}}function rt(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.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(n&&typeof n=="object"){let t=n;return typeof t.text=="string"?t.text:typeof t.output_text=="string"?t.output_text:""}return""}function Rt(n){if(typeof n.output_text=="string")return n.output_text;let t=[],e=r=>{if(r&&typeof r!="string"){if(Array.isArray(r)){r.forEach(e);return}if(typeof r=="object"){let i=r;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(n.output),t.join("")}function Mt(n,t){let r=(Array.isArray(n?.content)?n.content:[]).find(i=>i&&i.type==="tool_use"&&i.name===te);return r?typeof r.input=="string"?P(r.input,t):r.input&&typeof r.input=="object"?$(r.input):[]:null}function Nt(n){return(Array.isArray(n.content)?n.content:[]).map(e=>rt(e)).join("").trim()}function It(n){let e=(Array.isArray(n.choices)?n.choices:[])[0]||{},r=e.message;return rt(r?.content??e.text??"").trim()}function Dt(n){let r=((Array.isArray(n.candidates)?n.candidates:[])[0]||{}).content;return(Array.isArray(r?.parts)?r.parts:[]).map(a=>rt(a)).join("").trim()}function sr(n){return n.choices?.[0]?.delta?.content||""}function or(n){return n.type==="content_block_delta"&&n.delta?.text||""}function ne(n){switch(n){case"openai-chat":return sr;case"anthropic-messages":return or;default:return null}}function nt(n,t){let e=[],r=n.replace(/\r\n/g,` +${o}`;return{system:w,user:T}}function xe(n){let t=v(n),e=S[A(n)];return t.tokenLimitField||e?.tokenLimitField||"max_tokens"}function B(n,t,e,r){let i=!r||r.structured!==!1,a={model:N(e),max_tokens:Number(e.apiMaxTokens)||4096,system:n,messages:[{role:"user",content:t}]};return i&&(a.tools=[Pt()],a.tool_choice={type:"tool",name:re}),a}function _(n,t,e,r){let i=!r||r.structured!==!1,a={model:N(e),messages:[{role:"system",content:n},{role:"user",content:t}]};return a[xe(e)]=Number(e.apiMaxTokens)||4096,i&&(a.response_format=xt()),a}function z(n,t,e,r){let i=!r||r.structured!==!1,a={model:N(e),instructions:n,input:t,max_output_tokens:Number(e.apiMaxTokens)||4096};return i&&(a.text=St()),a}function W(n,t,e,r){let i=!r||r.structured!==!1,a={temperature:0,maxOutputTokens:Number(e.apiMaxTokens)||4096};return i&&(a.responseMimeType="application/json",a.responseJsonSchema=ne(!1)),{systemInstruction:{parts:[{text:n}]},contents:[{role:"user",parts:[{text:t}]}],generationConfig:a}}function rt(n){if(typeof n=="string")return n;if(Array.isArray(n))return n.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(n&&typeof n=="object"){let t=n;return typeof t.text=="string"?t.text:typeof t.output_text=="string"?t.output_text:""}return""}function Rt(n){if(typeof n.output_text=="string")return n.output_text;let t=[],e=r=>{if(r&&typeof r!="string"){if(Array.isArray(r)){r.forEach(e);return}if(typeof r=="object"){let i=r;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(n.output),t.join("")}function Mt(n,t){let r=(Array.isArray(n?.content)?n.content:[]).find(i=>i&&i.type==="tool_use"&&i.name===re);return r?typeof r.input=="string"?P(r.input,t):r.input&&typeof r.input=="object"?q(r.input):[]:null}function Nt(n){return(Array.isArray(n.content)?n.content:[]).map(e=>rt(e)).join("").trim()}function It(n){let e=(Array.isArray(n.choices)?n.choices:[])[0]||{},r=e.message;return rt(r?.content??e.text??"").trim()}function Dt(n){let r=((Array.isArray(n.candidates)?n.candidates:[])[0]||{}).content;return(Array.isArray(r?.parts)?r.parts:[]).map(a=>rt(a)).join("").trim()}function sr(n){return n.choices?.[0]?.delta?.content||""}function or(n){return n.type==="content_block_delta"&&n.delta?.text||""}function ie(n){switch(n){case"openai-chat":return sr;case"anthropic-messages":return or;default:return null}}function nt(n,t){let e=[],r=n.replace(/\r\n/g,` `),i=r.split(` `),a=r.endsWith(` @@ -97,15 +97,15 @@ ${o}`;return{system:w,user:T}}function xe(n){let t=v(n),e=S[A(n)];return t.token `)?i.slice(0,-1):i;for(let o of s){let l=[];for(let d of o.split(` `)){if(!d.startsWith("data:"))continue;let u=d.slice(d.startsWith("data: ")?6:5);l.push(u)}if(l.length===0)continue;let c=l.join(` -`);if(c.trim()!=="[DONE]")try{let d=JSON.parse(c),u=t(d);u&&e.push(u)}catch{}}return{deltas:e,rest:a}}async function lr(n,t,e,r,i,a,s){let o=await globalThis.fetch(n,{method:"POST",headers:t,body:JSON.stringify(e),signal:a});if(!o.ok){let m=await o.text();throw new Error(y(s||null,"errorProviderApiStatus",{label:"Streaming",status:o.status,excerpt:m.slice(0,500)}))}if(!o.body)throw new Error("Response has no body for streaming");let l=o.body.getReader(),c=new TextDecoder,d="",u="";try{for(;;){let{done:m,value:b}=await l.read();if(m)break;u+=c.decode(b,{stream:!0});let w=nt(u,r);u=w.rest;for(let T of w.deltas)d+=T;w.deltas.length>0&&i?.({accumulated:d,done:!1})}}finally{l.releaseLock()}return i?.({accumulated:d,done:!0}),d}async function it(n,t,e,r,i,a,s){if(typeof globalThis.fetch!="function")throw new Error("Streaming requires fetch API");let o=s?.streamingTimeoutMs??12e4,l=new AbortController,c=null;a&&(c=()=>l.abort(),a.addEventListener("abort",c,{once:!0}),a.aborted&&l.abort());let d=null,u=new Promise((m,b)=>{d=setTimeout(()=>{l.abort(),b(new Error(`Streaming timed out after ${o}ms`))},o)});try{return await Promise.race([lr(n,t,e,r,i,l.signal,s),u])}finally{d!==null&&clearTimeout(d),a&&c&&a.removeEventListener("abort",c)}}function Ot(n){let t=ne(n);if(!t)throw new Error(`Streaming not supported for format: ${n}`);return t}function ie(n,t){let e=n.replace(/\/+$/,"");for(let r of t)if(e.endsWith(r))return e;return e+t[0]}function at(n,t){let e=(n||"").trim();if(!e)return{};if(e.startsWith("{")){let i;try{i=JSON.parse(e)}catch(s){throw new Error(y(t||null,"errorCustomHeadersJsonParse",{error:s instanceof Error?s.message:String(s)}))}if(!i||typeof i!="object"||Array.isArray(i))throw new Error(y(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 r={};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(y(t||null,"errorCustomHeadersLineFormat"));let o=a.slice(0,s).trim(),l=a.slice(s+1).trim();o&&(r[o]=l)}return r}function cr(n){let t=Be(n);if(t==="none")return{};let e=Ct(n);if(!e){let r=(n.apiKeyEnvVar||v(n).envVar||"").trim(),i=r?y(n,"errorApiKeyEnvHint",{envVar:r}):"";throw new Error(y(n,"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 W(n,t){return{"content-type":"application/json",...cr(n),...t||{},...at(n.apiHeaders,n)}}function ur(n,t,e){if(n.json&&typeof n.json=="object")return n.json;try{return JSON.parse(n.text||"{}")}catch{throw new Error(y(e||null,"errorProviderNonJson",{label:t,excerpt:(n.text||"").slice(0,500)}))}}async function Lt(n,t,e,r,i,a){let s;try{s=await n({url:e,method:"POST",headers:r,body:JSON.stringify(i),throw:!1})}catch(o){throw new Error(y(a||null,"errorProviderRequestFailed",{label:t,error:o instanceof Error?o.message:String(o)}))}if(s.status>=400)throw new Error(y(a||null,"errorProviderApiStatus",{label:t,status:s.status,excerpt:(s.text||"").slice(0,500)}));return ur(s,t,a)}function dr(n){let t=n instanceof Error?n.message:String(n);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 Se(n,t,e,r,i,a,s){try{return await Lt(n,t,e,r,i,s)}catch(o){if(!a||!dr(o))throw o;return console.warn(`[parallel-reader] ${t} structured output rejected; retrying without structured output`,o),Lt(n,t+" fallback",e,r,a,s)}}async function pr(n,t,e,r){let i=ie(R(r),["/messages"]),a=await Se(n,"Anthropic-compatible",i,W(r,{"anthropic-version":"2023-06-01"}),B(t,e,r),B(t,e,r,{structured:!1}),r),s=Mt(a,r);return s||P(Nt(a),r)}async function hr(n,t,e,r){let i=ie(R(r),["/chat/completions"]),a=await Se(n,"OpenAI-compatible Chat",i,W(r),_(t,e,r),_(t,e,r,{structured:!1}),r);return P(It(a),r)}async function gr(n,t,e,r){let i=ie(R(r),["/responses"]),a=await Se(n,"OpenAI Responses",i,W(r),q(t,e,r),q(t,e,r,{structured:!1}),r);return P(Rt(a).trim(),r)}async function mr(n,t,e,r){let i=encodeURIComponent(N(r)),a=R(r);/:generateContent(?:\?|$)/.test(a)||(a=`${a.replace(/\/+$/,"")}/models/${i}:generateContent`);let s=W(r),o=await Se(n,"Google Gemini",a,s,z(t,e,r),z(t,e,r,{structured:!1}),r);return P(Dt(o),r)}async function ae(n,t,e,r){switch(A(r)){case"openai-chat":return hr(n,t,e,r);case"openai-responses":return gr(n,t,e,r);case"google-generative-ai":return mr(n,t,e,r);default:return pr(n,t,e,r)}}function Pe(n){if(!n.streaming)return!1;let t=A(n);return!!ne(t)}async function fr(n,t,e,r,i){let a=ie(R(e),["/chat/completions"]),s=W(e),o=_(n,t,e,{structured:!1});o.stream=!0;let l=Ot("openai-chat"),c=await it(a,s,o,l,r,i,e);return P(c.trim(),e)}async function yr(n,t,e,r,i){let a=ie(R(e),["/messages"]),s=W(e,{"anthropic-version":"2023-06-01"}),o=B(n,t,e,{structured:!1});o.stream=!0;let l=Ot("anthropic-messages"),c=await it(a,s,o,l,r,i,e);return P(c.trim(),e)}async function Bt(n,t,e,r,i){let a=A(e);switch(a){case"openai-chat":return fr(n,t,e,r,i);case"anthropic-messages":return yr(n,t,e,r,i);default:throw new Error(`Streaming not supported for format: ${a}`)}}async function _t(n,t){await ae(n,'\u53EA\u8F93\u51FA JSON\uFF1A{"cards":[]}','\u8FDE\u901A\u6027\u6D4B\u8BD5\uFF1A\u8BF7\u539F\u6837\u8F93\u51FA {"cards":[]}',t);let e=A(t);return`${v(t).label} / ${S[e].label}`}async function st(n,t,e,r){let{system:i,user:a}=Ae(n,t),s;switch(t.backend){case"claude-code":s=await Ft(i,a,t,e);break;case"codex":s=await Et(i,a,t,e);break;default:{if(Pe(t)){let c=new AbortController;e.onCancel(()=>c.abort()),s=await Bt(i,a,t,r,c.signal)}else s=await ae(Vt.requestUrl,i,a,t);break}}let o=s.map(l=>({title:l.title,level:2,anchor:l.anchor,gist:l.gist,startLine:G(n,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 ot(n,t){return t?.phase==="generating"&&n&&Oe(n.backend)?"cancelRequestedApiInFlight":"cancelRequested"}function lt(n){let t=[`## ${n.title}`];if(n.anchor){let e=n.anchor.replace(/\s+/g," ").trim();t.push(`> ${e}`)}return n.gist&&t.push(n.gist),n.bullets&&n.bullets.length>0&&t.push(n.bullets.map(e=>`- ${e}`).join(` +`);if(c.trim()!=="[DONE]")try{let d=JSON.parse(c),u=t(d);u&&e.push(u)}catch{}}return{deltas:e,rest:a}}async function lr(n,t,e,r,i,a,s){let o=await globalThis.fetch(n,{method:"POST",headers:t,body:JSON.stringify(e),signal:a});if(!o.ok){let g=await o.text();throw new Error(y(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,d="",u="";try{for(;;){let{done:g,value:b}=await l.read();if(g)break;u+=c.decode(b,{stream:!0});let w=nt(u,r);u=w.rest;for(let T of w.deltas)d+=T;w.deltas.length>0&&i?.({accumulated:d,done:!1})}}finally{l.releaseLock()}return i?.({accumulated:d,done:!0}),d}async function it(n,t,e,r,i,a,s){if(typeof globalThis.fetch!="function")throw new Error("Streaming requires fetch API");let o=s?.streamingTimeoutMs??12e4,l=new AbortController,c=null;a&&(c=()=>l.abort(),a.addEventListener("abort",c,{once:!0}),a.aborted&&l.abort());let d=null,u=new Promise((g,b)=>{d=setTimeout(()=>{l.abort(),b(new Error(`Streaming timed out after ${o}ms`))},o)});try{return await Promise.race([lr(n,t,e,r,i,l.signal,s),u])}finally{d!==null&&clearTimeout(d),a&&c&&a.removeEventListener("abort",c)}}function Ot(n){let t=ie(n);if(!t)throw new Error(`Streaming not supported for format: ${n}`);return t}function ae(n,t){let e=n.replace(/\/+$/,"");for(let r of t)if(e.endsWith(r))return e;return e+t[0]}function at(n,t){let e=(n||"").trim();if(!e)return{};if(e.startsWith("{")){let i;try{i=JSON.parse(e)}catch(s){throw new Error(y(t||null,"errorCustomHeadersJsonParse",{error:s instanceof Error?s.message:String(s)}))}if(!i||typeof i!="object"||Array.isArray(i))throw new Error(y(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 r={};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(y(t||null,"errorCustomHeadersLineFormat"));let o=a.slice(0,s).trim(),l=a.slice(s+1).trim();o&&(r[o]=l)}return r}function cr(n){let t=Be(n);if(t==="none")return{};let e=Ct(n);if(!e){let r=(n.apiKeyEnvVar||v(n).envVar||"").trim(),i=r?y(n,"errorApiKeyEnvHint",{envVar:r}):"";throw new Error(y(n,"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 Y(n,t){return{"content-type":"application/json",...cr(n),...t||{},...at(n.apiHeaders,n)}}function ur(n,t,e){if(n.json&&typeof n.json=="object")return n.json;try{return JSON.parse(n.text||"{}")}catch{throw new Error(y(e||null,"errorProviderNonJson",{label:t,excerpt:(n.text||"").slice(0,500)}))}}async function Lt(n,t,e,r,i,a){let s;try{s=await n({url:e,method:"POST",headers:r,body:JSON.stringify(i),throw:!1})}catch(o){throw new Error(y(a||null,"errorProviderRequestFailed",{label:t,error:o instanceof Error?o.message:String(o)}))}if(s.status>=400)throw new Error(y(a||null,"errorProviderApiStatus",{label:t,status:s.status,excerpt:(s.text||"").slice(0,500)}));return ur(s,t,a)}function dr(n){let t=n instanceof Error?n.message:String(n);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 Se(n,t,e,r,i,a,s){try{return await Lt(n,t,e,r,i,s)}catch(o){if(!a||!dr(o))throw o;return console.warn(`[parallel-reader] ${t} structured output rejected; retrying without structured output`,o),Lt(n,t+" fallback",e,r,a,s)}}async function pr(n,t,e,r){let i=ae(R(r),["/messages"]),a=await Se(n,"Anthropic-compatible",i,Y(r,{"anthropic-version":"2023-06-01"}),B(t,e,r),B(t,e,r,{structured:!1}),r),s=Mt(a,r);return s||P(Nt(a),r)}async function hr(n,t,e,r){let i=ae(R(r),["/chat/completions"]),a=await Se(n,"OpenAI-compatible Chat",i,Y(r),_(t,e,r),_(t,e,r,{structured:!1}),r);return P(It(a),r)}async function mr(n,t,e,r){let i=ae(R(r),["/responses"]),a=await Se(n,"OpenAI Responses",i,Y(r),z(t,e,r),z(t,e,r,{structured:!1}),r);return P(Rt(a).trim(),r)}async function gr(n,t,e,r){let i=encodeURIComponent(N(r)),a=R(r);/:generateContent(?:\?|$)/.test(a)||(a=`${a.replace(/\/+$/,"")}/models/${i}:generateContent`);let s=Y(r),o=await Se(n,"Google Gemini",a,s,W(t,e,r),W(t,e,r,{structured:!1}),r);return P(Dt(o),r)}async function se(n,t,e,r){switch(A(r)){case"openai-chat":return hr(n,t,e,r);case"openai-responses":return mr(n,t,e,r);case"google-generative-ai":return gr(n,t,e,r);default:return pr(n,t,e,r)}}function Pe(n){if(!n.streaming)return!1;let t=A(n);return!!ie(t)}async function fr(n,t,e,r,i){let a=ae(R(e),["/chat/completions"]),s=Y(e),o=_(n,t,e,{structured:!1});o.stream=!0;let l=Ot("openai-chat"),c=await it(a,s,o,l,r,i,e);return P(c.trim(),e)}async function yr(n,t,e,r,i){let a=ae(R(e),["/messages"]),s=Y(e,{"anthropic-version":"2023-06-01"}),o=B(n,t,e,{structured:!1});o.stream=!0;let l=Ot("anthropic-messages"),c=await it(a,s,o,l,r,i,e);return P(c.trim(),e)}async function Bt(n,t,e,r,i){let a=A(e);switch(a){case"openai-chat":return fr(n,t,e,r,i);case"anthropic-messages":return yr(n,t,e,r,i);default:throw new Error(`Streaming not supported for format: ${a}`)}}async function _t(n,t){await se(n,'\u53EA\u8F93\u51FA JSON\uFF1A{"cards":[]}','\u8FDE\u901A\u6027\u6D4B\u8BD5\uFF1A\u8BF7\u539F\u6837\u8F93\u51FA {"cards":[]}',t);let e=A(t);return`${v(t).label} / ${S[e].label}`}async function st(n,t,e,r){let{system:i,user:a}=Ae(n,t),s;switch(t.backend){case"claude-code":s=await Ft(i,a,t,e);break;case"codex":s=await Et(i,a,t,e);break;default:{if(Pe(t)){let c=new AbortController;e.onCancel(()=>c.abort()),s=await Bt(i,a,t,r,c.signal)}else s=await se(Vt.requestUrl,i,a,t);break}}let o=s.map(l=>({title:l.title,level:2,anchor:l.anchor,gist:l.gist,startLine:G(n,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 ot(n,t){return t?.phase==="generating"&&n&&Oe(n.backend)?"cancelRequestedApiInFlight":"cancelRequested"}function lt(n){let t=[`## ${n.title}`];if(n.anchor){let e=n.anchor.replace(/\s+/g," ").trim();t.push(`> ${e}`)}return n.gist&&t.push(n.gist),n.bullets&&n.bullets.length>0&&t.push(n.bullets.map(e=>`- ${e}`).join(` `)),t.join(` `)}function Gt(n){return[n.title,n.gist||"",...(n.bullets||[]).map(t=>"\u2022 "+t)].filter(Boolean).join(` -`)}function se(n,t){let e=[`# ${n||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let r of t||[])e.push(lt(r));return e.join(` +`)}function oe(n,t){let e=[`# ${n||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let r of t||[])e.push(lt(r));return e.join(` `)}var ct=require("obsidian");var F=require("obsidian");function I(n,t,e,r){let i=n.createEl("button",{cls:"parallel-reader-icon-button",attr:{type:"button","aria-label":e}});return i.title=e,typeof F.setIcon=="function"?(0,F.setIcon)(i,t):i.textContent=e,i.addEventListener("click",a=>{a.preventDefault(),a.stopPropagation(),Promise.resolve(r()).catch(s=>{console.error(s),new F.Notice(`${e} failed: ${s instanceof Error?s.message:String(s)}`)})}),i}function E(n,t,e,r,i){let a=n.createEl("button",{cls:i||"parallel-reader-text-button",attr:{type:"button"}});return t&&typeof F.setIcon=="function"&&(0,F.setIcon)(a,t),a.createSpan({text:e}),a.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),Promise.resolve(r()).catch(o=>{console.error(o),new F.Notice(`${e} failed: ${o instanceof Error?o.message:String(o)}`)})}),a}async function D(n,t){try{await navigator.clipboard.writeText(n),new F.Notice(t)}catch(e){new F.Notice(`Copy failed: ${e instanceof Error?e.message:String(e)}`)}}function Ut(n,t,e){return new Promise(r=>{let i=!1,a=l=>{i||(i=!0,r(l))},s=new ct.Modal(n);s.titleEl.setText(t),s.contentEl.createEl("p",{text:e});let o=s.contentEl.createDiv({cls:"modal-button-container"});o.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{s.close(),a(!1)}),o.createEl("button",{text:"OK",cls:"mod-cta"}).addEventListener("click",()=>{s.close(),a(!0)}),s.onClose=()=>a(!1),s.open()})}var Te=class extends ct.Modal{constructor(t,e,r,i){super(t),this.plugin=e,this.card=r,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||""),r=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"});E(a,null,this.plugin.t("editCardCancel"),()=>this.close(),"parallel-reader-text-button"),E(a,null,this.plugin.t("editCardSave"),async()=>{await this.onSave({title:e.value.trim()||this.card.title||"",gist:r.value.trim(),bullets:i.value.split(/\r?\n/).map(s=>s.trim()).filter(Boolean)}),this.close()},"parallel-reader-text-button")}createLabeledInput(t,e,r){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=r,a}createLabeledTextarea(t,e,r,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=r,s}};function ke(n,t,e){if(!Number.isFinite(t)||t<=0)return-1;let r=Math.floor(t),i=e<0?-1:1;return n<0||n>=r?i<0?r-1:0:Math.min(r-1,Math.max(0,n+i))}function Fe(n,t){if(!Array.isArray(n)||t<0||t>=n.length)return-1;let e=Number(n[t]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}function ut(n,t=80,e=.1){let r=Number(n?.top)||0,i=Math.max(0,Number(n?.height)||0),a=Math.min(Number(t)||0,i*e);return r+Math.max(0,a)}function jt(n){return{__brand:"ScheduleId",raw:n}}function br(n){return jt(typeof requestAnimationFrame=="function"?requestAnimationFrame(n):setTimeout(()=>n(Date.now()),16))}function Cr(n){if(typeof cancelAnimationFrame=="function"){cancelAnimationFrame(n.raw);return}clearTimeout(n.raw)}function dt(n,t=br,e=Cr){let r=null,i=()=>{r===null&&(r=t(()=>{r=null,n()}))};return i.cancel=()=>{r!==null&&(e(r),r=null)},i}var h=require("obsidian");async function wr(n){if(n.backend==="claude-code"){let t=L("claude",n.cliPath),{stdout:e}=await O(t,["--version"],"",1e4);return`claude @ ${t} ${e.trim()}`}if(n.backend==="codex"){let t=L("codex",n.cliPath),{stdout:e}=await O(t,["--version"],"",1e4);return`codex @ ${t} -${e.trim()}`}return _t(h.requestUrl,n)}var Ee=class extends h.PluginSettingTab{constructor(t,e){super(t,e),this.plugin=e}tr(t,e){return this.plugin.t(t,e)}display(){let{containerEl:t}=this;t.empty(),new h.Setting(t).setName(this.tr("settingsTitle")).setHeading(),this.renderGeneralSection(t);let e=this.plugin.settings.backend==="claude-code"||this.plugin.settings.backend==="codex";this.renderBackendSection(t,e),this.renderPromptSection(t,e),this.renderActionsSection(t,e),this.renderCacheSection(t)}renderGeneralSection(t){new h.Setting(t).setName(this.tr("settingUiLanguageName")).setDesc(this.tr("settingUiLanguageDesc")).addDropdown(e=>{for(let[r,i]of Object.entries(De))e.addOption(r,i);return e.setValue(this.plugin.settings.uiLanguage||g.uiLanguage).onChange(async r=>{this.plugin.settings.uiLanguage=r,await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(this.tr("settingBackendName")).setDesc(this.tr("settingBackendDesc")).addDropdown(e=>e.addOption("api","API / provider").addOption("claude-code","Claude code CLI").addOption("codex","Codex CLI").setValue(this.plugin.settings.backend).onChange(async r=>{this.plugin.settings.backend=r,r==="api"&&!this.plugin.settings.apiBaseUrl&&(this.plugin.settings=_e(this.plugin.settings,this.plugin.settings.apiProvider||"anthropic")),await this.plugin.saveSettings(),this.display()}))}renderBackendSection(t,e){if(e){new h.Setting(t).setName(this.tr("settingCliPathName")).setDesc(this.tr("settingCliPathDesc")).addText(i=>i.setPlaceholder(this.tr("settingCliPathPlaceholder")).setValue(this.plugin.settings.cliPath).onChange(a=>{this.plugin.settings.cliPath=a.trim(),this.plugin.saveSettingsDebounced()}));return}let r=v(this.plugin.settings);new h.Setting(t).setName(this.tr("apiProviderHeader")).setHeading(),new h.Setting(t).setName(this.tr("settingProviderPresetName")).setDesc(this.tr("settingProviderPresetDesc")).addDropdown(i=>{for(let[a,s]of Object.entries(M))i.addOption(a,s.label);return i.setValue(this.plugin.settings.apiProvider).onChange(async a=>{this.plugin.settings=_e(this.plugin.settings,a),await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(this.tr("settingApiFormatName")).setDesc(this.tr("settingApiFormatDesc")).addDropdown(i=>{for(let[a,s]of Object.entries(S))i.addOption(a,s.label);return i.setValue(A(this.plugin.settings)).onChange(async a=>{this.plugin.settings.apiFormat=a,await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(this.tr("settingBaseUrlName")).setDesc(this.tr("settingBaseUrlDesc")).addText(i=>i.setPlaceholder((this.plugin.settings.apiProvider||"").startsWith("custom-")?"https://your-provider.example/v1":r.baseUrl||S[A(this.plugin.settings)].defaultBaseUrl).setValue(this.plugin.settings.apiBaseUrl).onChange(a=>{this.plugin.settings.apiBaseUrl=a.trim(),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingApiKeyName")).setDesc(this.tr("settingApiKeyDesc")).addText(i=>(i.inputEl.type="password",i.setValue(this.plugin.settings.apiKey).onChange(a=>{this.plugin.settings.apiKey=a.trim(),this.plugin.saveSettingsDebounced()}))),new h.Setting(t).setName(this.tr("settingApiKeyEnvName")).setDesc(this.tr("settingApiKeyEnvDesc")).addText(i=>i.setPlaceholder(r.envVar||"OPENAI_API_KEY").setValue(this.plugin.settings.apiKeyEnvVar).onChange(a=>{this.plugin.settings.apiKeyEnvVar=a.trim(),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingAuthTypeName")).setDesc(this.tr("settingAuthTypeDesc")).addDropdown(i=>{for(let[a,s]of Object.entries(Le))i.addOption(a,s);return i.setValue(this.plugin.settings.apiAuthType||"auto").onChange(async a=>{this.plugin.settings.apiAuthType=a,await this.plugin.saveSettings()})}),new h.Setting(t).setName(this.tr("settingHeadersName")).setDesc(this.tr("settingHeadersDesc")).addTextArea(i=>i.setValue(this.plugin.settings.apiHeaders).onChange(a=>{this.plugin.settings.apiHeaders=a,this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingMaxTokensName")).addText(i=>i.setValue(String(this.plugin.settings.apiMaxTokens)).onChange(a=>{let s=parseInt(a,10);!Number.isNaN(s)&&s>0&&(this.plugin.settings.apiMaxTokens=s,this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(this.tr("settingStreamingName")).setDesc(this.tr("settingStreamingDesc")).addToggle(i=>i.setValue(this.plugin.settings.streaming??!0).onChange(async a=>{this.plugin.settings.streaming=a,await this.plugin.saveSettings()})),new h.Setting(t).setName(this.tr("settingStreamingTimeoutName")).setDesc(this.tr("settingStreamingTimeoutDesc")).addText(i=>i.setPlaceholder(String(g.streamingTimeoutMs)).setValue(String(this.plugin.settings.streamingTimeoutMs||g.streamingTimeoutMs)).onChange(a=>{this.plugin.settings.streamingTimeoutMs=X(a),this.plugin.saveSettingsDebounced()}))}renderPromptSection(t,e){new h.Setting(t).setName(this.tr("settingModelName")).setDesc(e?this.tr("settingModelDescCli"):this.tr("settingModelDescApi")).addText(r=>r.setPlaceholder(e?g.model:v(this.plugin.settings).model||"Model-id").setValue(this.plugin.settings.model).onChange(i=>{this.plugin.settings.model=i.trim()||(e?g.model:""),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingMaxInputName")).setDesc(this.tr("settingMaxInputDesc")).addText(r=>r.setValue(String(this.plugin.settings.maxDocChars||g.maxDocChars)).onChange(i=>{let a=parseInt(i,10);!Number.isNaN(a)&&a>=1e3&&(this.plugin.settings.maxDocChars=a,this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(this.tr("promptHeader")).setHeading(),new h.Setting(t).setName(this.tr("settingPromptLanguageName")).setDesc(this.tr("settingPromptLanguageDesc")).addDropdown(r=>{for(let[i,a]of Object.entries(Y))r.addOption(i,a);return r.setValue(this.plugin.settings.promptLanguage||g.promptLanguage).onChange(async i=>{this.plugin.settings.promptLanguage=i,await this.plugin.saveSettings()})}),new h.Setting(t).setName(this.tr("settingCardRangeName")).setDesc(this.tr("settingCardRangeDesc")).addText(r=>r.setPlaceholder("Min").setValue(String(this.plugin.settings.minCards||g.minCards)).onChange(i=>{let a=parseInt(i,10);!Number.isNaN(a)&&a>0&&(this.plugin.settings.minCards=a,this.plugin.settings.maxCardsr.setPlaceholder("Max").setValue(String(this.plugin.settings.maxCards||g.maxCards)).onChange(i=>{let a=parseInt(i,10);!Number.isNaN(a)&&a>0&&(this.plugin.settings.maxCards=Math.max(a,this.plugin.settings.minCards||g.minCards),this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(this.tr("settingCustomPromptName")).setDesc(this.tr("settingCustomPromptDesc")).addTextArea(r=>(r.inputEl.rows=8,r.setPlaceholder(this.tr("settingCustomPromptPlaceholder")).setValue(this.plugin.settings.customSystemPrompt||"").onChange(i=>{this.plugin.settings.customSystemPrompt=i,this.plugin.saveSettingsDebounced()})))}renderActionsSection(t,e){new h.Setting(t).setName(this.tr("settingTestBackendName")).setDesc(e?this.tr("settingTestBackendDescCli"):this.tr("settingTestBackendDescApi")).addButton(r=>r.setButtonText("Test").onClick(async()=>{try{let i=await wr(this.plugin.settings);new h.Notice(`\u2713 ${i.slice(0,180)}`,8e3)}catch(i){new h.Notice(this.tr("backendTestFailed",{error:i instanceof Error?i.message:String(i)}),1e4)}})),new h.Setting(t).setName(this.tr("settingExportFolderName")).setDesc(this.tr("settingExportFolderDesc")).addText(r=>r.setValue(this.plugin.settings.exportFolder).onChange(i=>{this.plugin.settings.exportFolder=i.trim()||g.exportFolder,this.plugin.saveSettingsDebounced()}))}renderCacheSection(t){new h.Setting(t).setName(this.tr("cacheHeader")).setHeading(),new h.Setting(t).setName(this.tr("settingMaxCacheName")).setDesc(this.tr("settingMaxCacheDesc")).addText(r=>{r.setValue(String(this.plugin.settings.maxCacheEntries||H));let i=async()=>{let a=parseInt(r.getValue(),10);if(Number.isFinite(a)&&a>0){this.plugin.settings.maxCacheEntries=a,await this.plugin.saveSettings();let s=await this.plugin.pruneCacheIfNeeded();s.length>0&&new h.Notice(this.tr("cachePruned",{count:s.length})),this.display()}};return r.inputEl.addEventListener("change",()=>{i().catch(a=>console.error("[parallel-reader] failed to save cache settings",a))}),r.inputEl.addEventListener("keydown",a=>{a.key==="Enter"&&r.inputEl.blur()}),r});let e=Object.keys(this.plugin.cache).length;new h.Setting(t).setName(this.tr("cachedNotesName",{count:e})).setDesc(this.tr("cachedNotesDesc")).addButton(r=>r.setButtonText(this.tr("clearAllCacheButton")).setWarning().onClick(async()=>{let i=Object.keys(this.plugin.cache).length;await this.plugin.cacheClear(),new h.Notice(this.tr("cacheClearedAll",{count:i})),this.display()}))}};function pt(n){return String(n||"").split("/").map(t=>t.trim()).filter(t=>!!t&&t!==".."&&t!==".").join("/")}function ht(n){let t=pt(n);if(!t)return[];let e=t.split("/");return e.map((r,i)=>e.slice(0,i+1).join("/"))}async function Ht(n,t){for(let e of ht(t))if(!n.vault.getAbstractFileByPath(e))try{await n.vault.createFolder(e)}catch(r){if(!n.vault.getAbstractFileByPath(e))throw r}}var x=require("obsidian");var V="parallel-reader-view",Re=class extends x.ItemView{constructor(e,r){super(e);this.stale=!1;this.loadingMessage="";this.errorMessage="";this.plugin=r,this.sections=[],this.sourceFile=null,this.cards=[],this.activeIdx=-1}getViewType(){return V}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",r=>this.handleKeydown(r)),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 r=e.createDiv({cls:"parallel-reader-empty"});r.createEl("h3",{text:this.plugin.t("appTitle")}),r.createEl("p",{text:this.plugin.t("emptyOpenNote")}),r.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,r,i){this.sourceFile=e,this.sections=r,this.stale=!!i,this.loadingMessage="",this.errorMessage="",this.render()}renderLoading(e,r){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage=r||this.plugin.t("loadingDefault"),this.errorMessage="",this.render()}renderStreamingPreview(e,r){this.sourceFile?.path!==e.path&&(this.sourceFile=e);let i=this.containerEl.children[1],a=i.querySelector(".parallel-reader-streaming-preview");if(a){let m=a.querySelector("pre");m&&(m.textContent=r.slice(-2e3));let b=a.querySelector(".parallel-reader-stream-counter");b&&(b.textContent=`${r.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"});I(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 d=c.createEl("div",{cls:"parallel-reader-state-title"});d.createSpan({text:this.plugin.t("loadingGenerating")+" "}),d.createSpan({cls:"parallel-reader-stream-counter",text:`${r.length} chars`});let u=c.createEl("pre",{cls:"parallel-reader-stream-text"});u.textContent=r.slice(-2e3)}renderError(e,r){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage=r||this.plugin.t("errorTitle"),this.render()}renderEmptyWithHint(e){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage="";let r=this.containerEl.children[1];r.empty();let i=r.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")}),E(i,null,this.plugin.t("actionGenerate"),()=>{this.plugin.isGeneratingFile(e)||this.plugin.runForFile(e,!1)})}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)?I(a,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(this.sourceFile)}):I(a,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0)),I(a,"copy",this.plugin.t("actionCopyAll"),()=>this.plugin.copyCurrentViewMarkdown()),I(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")}),E(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 parallel-reader-selectable"});let c=l.createDiv({cls:"parallel-reader-error-actions"});E(c,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-text-button"),E(c,"copy",this.plugin.t("actionCopyError"),()=>D(this.errorMessage,this.plugin.t("actionCopyError")),"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)=>{this.cards.push(this.renderCard(s,l,c,o))}),this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].addClass("is-active")}renderCard(e,r,i,a){let s=e.createDiv({cls:"parallel-reader-card"});s.dataset.idx=String(i),r.startLine<0&&s.addClass("parallel-reader-card-unanchored");let o=s.createEl("div",{cls:"parallel-reader-card-title"});if(o.createSpan({text:r.title}),r.startLine<0&&o.createEl("span",{text:" \u26A0",cls:"parallel-reader-warn",title:this.plugin.t("anchorMismatch")}),r.gist){let c=s.createEl("div",{cls:"parallel-reader-gist"});x.MarkdownRenderer.render(this.app,r.gist,c,a,this).catch(()=>{c.setText(r.gist)})}let l=r.bullets||[];if(l.length>0){let c=s.createEl("div",{cls:"parallel-reader-bullets-md"}),d=l.map(u=>`- ${u}`).join(` -`);x.MarkdownRenderer.render(this.app,d,c,a,this).catch(()=>{c.setText(d)})}else r.gist||s.createEl("div",{cls:"parallel-reader-empty-li",text:this.plugin.t("emptyCard")});return s.addEventListener("click",c=>{let d=window.getSelection();if(d&&d.toString().length>0)return;let u=c.target;u&&u.tagName==="A"||r.startLine>=0&&this.plugin.scrollEditorToLine(r.startLine,this.sourceFile)}),s.addEventListener("contextmenu",c=>{c.preventDefault(),this.showCardContextMenu(c,r,i)}),s}showCardContextMenu(e,r,i){let a=new x.Menu;a.addItem(s=>s.setTitle(this.plugin.t("menuCopyMarkdown")).setIcon("copy").onClick(()=>D(lt(r),this.plugin.t("copiedMarkdown")))),a.addItem(s=>s.setTitle(this.plugin.t("menuCopyPlain")).setIcon("clipboard-copy").onClick(()=>D(Gt(r),this.plugin.t("copiedPlain")))),r.anchor&&a.addItem(s=>s.setTitle(this.plugin.t("menuCopyAnchor")).setIcon("quote-glyph").onClick(()=>D(r.anchor,this.plugin.t("copiedAnchor")))),a.addSeparator(),r.startLine>=0&&a.addItem(s=>s.setTitle(this.plugin.t("menuJumpSource")).setIcon("arrow-right").onClick(()=>this.plugin.scrollEditorToLine(r.startLine,this.sourceFile))),a.addSeparator(),a.addItem(s=>s.setTitle(this.plugin.t("menuEditCard")).setIcon("pencil").onClick(()=>this.openEditCardModal(i))),a.addItem(s=>s.setTitle(this.plugin.t("menuDeleteCard")).setIcon("trash").onClick(()=>this.deleteCard(i))),a.showAtMouseEvent(e)}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 r=ke(this.activeIdx,this.sections.length,e);return this.setActiveSection(r),this.focusSummaryPane(),r}jumpToActiveSection(){let e=Fe(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 r=ye(this.sections,e);if(r.length===this.sections.length)return!1;let i=this.sections.length;return this.sections=r,this.activeIdx=be(e,i,this.activeIdx),await this.plugin.cacheReplaceCards(this.sourceFile.path,r),this.render(),new x.Notice(this.plugin.t("cardDeleted")),!0}openEditCardModal(e){return!this.sourceFile||!this.sections[e]?!1:(new Te(this.app,this.plugin,this.sections[e],async r=>{await this.updateCard(e,r)}).open(),!0)}async updateCard(e,r){if(!this.sourceFile)return!1;let i=Ce(this.sections,e,r);return i.length!==this.sections.length?!1:(this.sections=i,await this.plugin.cacheReplaceCards(this.sourceFile.path,i),this.render(),new x.Notice(this.plugin.t("cardSaved")),!0)}async exportToVault(){if(!this.sourceFile)return;let e=pt(this.plugin.settings.exportFolder),r=`${this.sourceFile.basename} - ${this.plugin.t("displayName")}.md`,i=`${e}/${r}`,a=["---",`source: [[${this.sourceFile.basename}]]`,`generated: ${new Date().toISOString().slice(0,10)}`,"tool: parallel-reader","---","",se(`${this.sourceFile.basename} \xB7 ${this.plugin.t("displayName")}`,this.sections),""].join(` -`),s=this.plugin.app;await Ht(s,e);let o=s.vault.getAbstractFileByPath(i);o instanceof x.TFile?await s.vault.modify(o,a):await s.vault.create(i,a),new x.Notice(this.plugin.t("exported",{path:i}))}};var gt=class extends p.Plugin{constructor(){super(...arguments);this.activeBatch=null;this._scrollDispose=null;this._settingsSaveTimer=null}get cache(){return this.cacheManager.cache}t(e,r){return y(this.settings||g,e,r)}async onload(){await this.loadSettings(),this.jobs=new ee,this.addRibbonIcon("book-open",this.t("ribbonOpen"),async()=>{let e=this.getActiveView();await this.ensureView()&&e?.file&&await this.syncViewToFile(e.file)}),this.registerView(V,e=>new Re(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&&await 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){new p.Notice(this.t("noCurrentNote"));return}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 Ee(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).catch(r=>console.error("[parallel-reader] sync view",r))})),this.registerEvent(this.app.workspace.on("file-menu",(e,r)=>this.addFileMenuItems(e,r))),this.registerEvent(this.app.vault.on("rename",(e,r)=>{e instanceof p.TFile&&this.handleFileRename(e,r).catch(i=>console.error("[parallel-reader] file rename",i))})),this.registerEvent(this.app.vault.on("delete",e=>{e instanceof p.TFile&&this.handleFileDelete(e).catch(r=>console.error("[parallel-reader] file delete",r))})),this.bindScrollSync()}onunload(){this.flushSettingsSave().catch(e=>console.error("[parallel-reader] flush settings on unload",e)),this.flushCacheSave().catch(e=>console.error("[parallel-reader] flush cache on unload",e))}async loadSettings(){let r=(await this.loadData()||{}).settings||{};this.settings=de(Object.assign({},g,r)),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(r=>console.error("[parallel-reader] failed to save settings",r))},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,r){return this.cacheManager.replaceCards(e,r)}async cacheClear(){return this.cacheManager.clear()}async pruneCacheIfNeeded(){return this.cacheManager.pruneIfNeeded()}async ensureView(){let{workspace:e}=this.app,r=e.getLeavesOfType(V)[0];if(!r){let i=e.getRightLeaf(!1);if(!i)return new p.Notice(this.t("viewOpenFailed")),null;r=i,await r.setViewState({type:V,active:!0})}return await e.revealLeaf(r),r.view}getActiveView(){return this.app.workspace.getActiveViewOfType(p.MarkdownView)}getParallelView(){return this.app.workspace.getLeavesOfType(V)[0]?.view}moveActiveCard(e){let r=this.getParallelView();return r?.sections.length?r.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 r=this.jobs.get(e.path),i=ot(this.settings,r),a=this.jobs.cancel(e.path);return a&&this.activeBatch?.currentPath===e.path&&ge(this.activeBatch),new p.Notice(a?this.t(i):this.t("noCancelableJob")),a}requestBatchCancellation(){return ge(this.activeBatch)}viewIsShowingFile(e,r){return!!e&&!!r&&e.sourceFile?.path===r.path}activeFileStillMatches(e){let r=this.getActiveView();return!r?.file||r.file.path===e.path}cancelActiveGeneration(){if(this.requestBatchCancellation()){let i=this.activeBatch?.currentPath;i&&this.jobs.cancel(i),new p.Notice(this.t("batchCancelRequested"));return}let e=this.getActiveView();if(e?.file&&this.cancelGenerationForFile(e.file))return;let r=this.getParallelView();r?.sourceFile?this.cancelGenerationForFile(r.sourceFile):new p.Notice(this.t("noCancelableJob"))}confirmRegenerateEditedCards(){return Ut(this.app,this.t("displayName"),this.t("confirmRegenerateEditedCards"))}addFileMenuItems(e,r){!(r instanceof p.TFile)||!r.path.endsWith(".md")||(e.addSeparator(),e.addItem(i=>i.setTitle(this.t("fileMenuGenerate")).setIcon("book-open").onClick(()=>this.runForFile(r,!1))),e.addItem(i=>i.setTitle(this.t("fileMenuRegen")).setIcon("refresh-cw").onClick(()=>this.runForFile(r,!0))),this.cacheManager.get(r.path)&&e.addItem(i=>i.setTitle(this.t("fileMenuClear")).setIcon("trash").onClick(async()=>{await this.cacheManager.delete(r.path),new p.Notice(this.t("cacheClearedFile",{name:r.basename}))})))}async handleFileRename(e,r){if(!(e instanceof p.TFile)||!r)return;let i=r.endsWith(".md"),a=e.path.endsWith(".md");if(!i&&!a)return;if(i&&!a){await this.cacheManager.delete(r);let o=this.getParallelView();o&&o.sourceFile?.path===r&&o.renderEmpty();return}if(!i)return;await this.cacheManager.move(r,e.path);let s=this.getParallelView();s?.sourceFile&&(s.sourceFile.path===r||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 r=this.getParallelView();r?.sourceFile?.path===e.path&&r.renderEmpty()}async exportCurrentView(){let e=this.getActiveView(),r=await this.ensureView();if(r){if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new p.Notice(this.t("noExportContent"));return}await r.exportToVault()}}async copyCurrentViewMarkdown(){let e=this.getActiveView(),r=await this.ensureView();if(r){if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new p.Notice(this.t("noCopyContent"));return}await D(se(`${r.sourceFile.basename} \xB7 ${this.t("displayName")}`,r.sections),this.t("copiedAllMarkdown"))}}async runForActiveFile(e){let r=this.getActiveView();if(!r?.file){new p.Notice(this.t("openNoteFirst"));return}return this.runForFile(r.file,e)}async runForFile(e,r){if(!e){new p.Notice(this.t("openNoteFirst"));return}if(this.jobs.isRunning(e.path)){new p.Notice(this.t("alreadyGenerating"));return}if(Ye(this.cacheManager.get(e.path),r)&&!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"),!r){let u=this.cacheManager.get(e.path);if(u&&J(u,s,this.settings)){this.cacheTouch(e.path),this.activeFileStillMatches(e)&&i.loadFor(e,Xe(s,u.cards),!1);return}}i.renderLoading(e,this.t("loadingGenerating"));let o=Number(this.settings.maxDocChars)||g.maxDocChars;s.length>o&&new p.Notice(this.t("longNoteTruncated",{count:o})),new p.Notice(this.t("generatingNotice")),a.setPhase("generating");let l=await st(s,this.settings,a,this.streamProgressFor(i,e));if(a.throwIfCancelled(),l.length===0){new p.Notice(this.t("noCardsReturned"));return}let c=l.map(u=>({title:u.title,anchor:u.anchor,gist:u.gist,bullets:u.bullets}));a.setPhase("saving"),await this.cacheManager.put(e.path,s,c,this.settings),a.throwIfCancelled(),this.viewIsShowingFile(i,e)&&i.loadFor(e,l,!1);let d=l.filter(u=>u.startLine<0).length;new p.Notice(this.t("generationDone",{count:l.length,suffix:d?this.t("unanchoredSuffix",{count:d}):""}))}).catch(a=>this.handleGenerationError(a,e,i))}streamProgressFor(e,r){if(e)return i=>{!i.done&&this.viewIsShowingFile(e,r)&&e.renderStreamingPreview(r,i.accumulated)}}handleGenerationError(e,r,i){if(e instanceof K){new p.Notice(e.message);return}if(e instanceof k){i&&this.viewIsShowingFile(i,r)&&i.renderError(r,this.t("cancelledError")),new p.Notice(this.t("cancelled"));return}let a=Qe(e),s=e instanceof Error?e.message:String(e);console.error(e),i&&this.viewIsShowingFile(i,r)&&i.renderError(r,s),new p.Notice(this.t("generationFailed",{kind:a==="unknown"?"":` (${a})`,error:s}))}async runBatchForFolder(){if(this.activeBatch){new p.Notice(this.t("batchAlreadyRunning"));return}let e=await At(this.app,this.t("batchSelectFolder"),this.t("batchFolderPrompt"));if(e===null)return;let r=Ge(e,l=>{let c=this.app.vault.getAbstractFileByPath(l);return!!c&&!(c instanceof p.TFile)});if(!r.valid){let l=r.reason==="unsafe"?"batchInvalidFolderInput":"batchFolderNotFound";new p.Notice(this.t(l,{path:r.folderPath||e}));return}let i=Ue(this.app.vault.getMarkdownFiles(),r.folderPath);if(i.length===0){new p.Notice(this.t("batchNoMarkdown"));return}let a=ze();this.activeBatch=a;let s=He(i.length);try{for(let l=0;lthis.handleEditorScroll(e));a.addEventListener("scroll",s,{passive:!0}),this._scrollDispose=()=>{s.cancel(),a.removeEventListener("scroll",s)}}handleEditorScroll(e){let r=this.getParallelView();if(!r||!e.file||r.sourceFile?.path!==e.file.path)return;let i=e.editor,a=i&&i.cm;if(!a?.scrollDOM)return;let s=a.scrollDOM.getBoundingClientRect(),o=ut(s),l=0;try{let d=a.posAtCoords({x:s.left+20,y:o});d!=null&&(l=a.state.doc.lineAt(d).number-1)}catch{return}let c=-1;for(let d=0;d{for(let[r,i]of Object.entries(De))e.addOption(r,i);return e.setValue(this.plugin.settings.uiLanguage||m.uiLanguage).onChange(async r=>{this.plugin.settings.uiLanguage=r,await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(this.tr("settingBackendName")).setDesc(this.tr("settingBackendDesc")).addDropdown(e=>e.addOption("api","API / provider").addOption("claude-code","Claude code CLI").addOption("codex","Codex CLI").setValue(this.plugin.settings.backend).onChange(async r=>{this.plugin.settings.backend=r,r==="api"&&!this.plugin.settings.apiBaseUrl&&(this.plugin.settings=_e(this.plugin.settings,this.plugin.settings.apiProvider||"anthropic")),await this.plugin.saveSettings(),this.display()}))}renderBackendSection(t,e){if(e){new h.Setting(t).setName(this.tr("settingCliPathName")).setDesc(this.tr("settingCliPathDesc")).addText(i=>i.setPlaceholder(this.tr("settingCliPathPlaceholder")).setValue(this.plugin.settings.cliPath).onChange(a=>{this.plugin.settings.cliPath=a.trim(),this.plugin.saveSettingsDebounced()}));return}let r=v(this.plugin.settings);new h.Setting(t).setName(this.tr("apiProviderHeader")).setHeading(),new h.Setting(t).setName(this.tr("settingProviderPresetName")).setDesc(this.tr("settingProviderPresetDesc")).addDropdown(i=>{for(let[a,s]of Object.entries(M))i.addOption(a,s.label);return i.setValue(this.plugin.settings.apiProvider).onChange(async a=>{this.plugin.settings=_e(this.plugin.settings,a),await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(this.tr("settingApiFormatName")).setDesc(this.tr("settingApiFormatDesc")).addDropdown(i=>{for(let[a,s]of Object.entries(S))i.addOption(a,s.label);return i.setValue(A(this.plugin.settings)).onChange(async a=>{this.plugin.settings.apiFormat=a,await this.plugin.saveSettings(),this.display()})}),new h.Setting(t).setName(this.tr("settingBaseUrlName")).setDesc(this.tr("settingBaseUrlDesc")).addText(i=>i.setPlaceholder((this.plugin.settings.apiProvider||"").startsWith("custom-")?"https://your-provider.example/v1":r.baseUrl||S[A(this.plugin.settings)].defaultBaseUrl).setValue(this.plugin.settings.apiBaseUrl).onChange(a=>{this.plugin.settings.apiBaseUrl=a.trim(),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingApiKeyName")).setDesc(this.tr("settingApiKeyDesc")).addText(i=>(i.inputEl.type="password",i.setValue(this.plugin.settings.apiKey).onChange(a=>{this.plugin.settings.apiKey=a.trim(),this.plugin.saveSettingsDebounced()}))),new h.Setting(t).setName(this.tr("settingApiKeyEnvName")).setDesc(this.tr("settingApiKeyEnvDesc")).addText(i=>i.setPlaceholder(r.envVar||"OPENAI_API_KEY").setValue(this.plugin.settings.apiKeyEnvVar).onChange(a=>{this.plugin.settings.apiKeyEnvVar=a.trim(),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingAuthTypeName")).setDesc(this.tr("settingAuthTypeDesc")).addDropdown(i=>{for(let[a,s]of Object.entries(Le))i.addOption(a,s);return i.setValue(this.plugin.settings.apiAuthType||"auto").onChange(async a=>{this.plugin.settings.apiAuthType=a,await this.plugin.saveSettings()})}),new h.Setting(t).setName(this.tr("settingHeadersName")).setDesc(this.tr("settingHeadersDesc")).addTextArea(i=>i.setValue(this.plugin.settings.apiHeaders).onChange(a=>{this.plugin.settings.apiHeaders=a,this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingMaxTokensName")).addText(i=>i.setValue(String(this.plugin.settings.apiMaxTokens)).onChange(a=>{let s=parseInt(a,10);!Number.isNaN(s)&&s>0&&(this.plugin.settings.apiMaxTokens=s,this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(this.tr("settingStreamingName")).setDesc(this.tr("settingStreamingDesc")).addToggle(i=>i.setValue(this.plugin.settings.streaming??!0).onChange(async a=>{this.plugin.settings.streaming=a,await this.plugin.saveSettings()})),new h.Setting(t).setName(this.tr("settingStreamingTimeoutName")).setDesc(this.tr("settingStreamingTimeoutDesc")).addText(i=>i.setPlaceholder(String(m.streamingTimeoutMs)).setValue(String(this.plugin.settings.streamingTimeoutMs||m.streamingTimeoutMs)).onChange(a=>{this.plugin.settings.streamingTimeoutMs=Z(a),this.plugin.saveSettingsDebounced()}))}renderPromptSection(t,e){new h.Setting(t).setName(this.tr("settingModelName")).setDesc(e?this.tr("settingModelDescCli"):this.tr("settingModelDescApi")).addText(r=>r.setPlaceholder(e?m.model:v(this.plugin.settings).model||"Model-id").setValue(this.plugin.settings.model).onChange(i=>{this.plugin.settings.model=i.trim()||(e?m.model:""),this.plugin.saveSettingsDebounced()})),new h.Setting(t).setName(this.tr("settingMaxInputName")).setDesc(this.tr("settingMaxInputDesc")).addText(r=>r.setValue(String(this.plugin.settings.maxDocChars||m.maxDocChars)).onChange(i=>{let a=parseInt(i,10);!Number.isNaN(a)&&a>=1e3&&(this.plugin.settings.maxDocChars=a,this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(this.tr("promptHeader")).setHeading(),new h.Setting(t).setName(this.tr("settingPromptLanguageName")).setDesc(this.tr("settingPromptLanguageDesc")).addDropdown(r=>{for(let[i,a]of Object.entries(X))r.addOption(i,a);return r.setValue(this.plugin.settings.promptLanguage||m.promptLanguage).onChange(async i=>{this.plugin.settings.promptLanguage=i,await this.plugin.saveSettings()})}),new h.Setting(t).setName(this.tr("settingCardRangeName")).setDesc(this.tr("settingCardRangeDesc")).addText(r=>r.setPlaceholder("Min").setValue(String(this.plugin.settings.minCards||m.minCards)).onChange(i=>{let a=parseInt(i,10);!Number.isNaN(a)&&a>0&&(this.plugin.settings.minCards=a,this.plugin.settings.maxCardsr.setPlaceholder("Max").setValue(String(this.plugin.settings.maxCards||m.maxCards)).onChange(i=>{let a=parseInt(i,10);!Number.isNaN(a)&&a>0&&(this.plugin.settings.maxCards=Math.max(a,this.plugin.settings.minCards||m.minCards),this.plugin.saveSettingsDebounced())})),new h.Setting(t).setName(this.tr("settingCustomPromptName")).setDesc(this.tr("settingCustomPromptDesc")).addTextArea(r=>(r.inputEl.rows=8,r.setPlaceholder(this.tr("settingCustomPromptPlaceholder")).setValue(this.plugin.settings.customSystemPrompt||"").onChange(i=>{this.plugin.settings.customSystemPrompt=i,this.plugin.saveSettingsDebounced()})))}renderActionsSection(t,e){new h.Setting(t).setName(this.tr("settingTestBackendName")).setDesc(e?this.tr("settingTestBackendDescCli"):this.tr("settingTestBackendDescApi")).addButton(r=>r.setButtonText("Test").onClick(async()=>{try{let i=await wr(this.plugin.settings);new h.Notice(`\u2713 ${i.slice(0,180)}`,8e3)}catch(i){new h.Notice(this.tr("backendTestFailed",{error:i instanceof Error?i.message:String(i)}),1e4)}})),new h.Setting(t).setName(this.tr("settingExportFolderName")).setDesc(this.tr("settingExportFolderDesc")).addText(r=>r.setValue(this.plugin.settings.exportFolder).onChange(i=>{this.plugin.settings.exportFolder=i.trim()||m.exportFolder,this.plugin.saveSettingsDebounced()}))}renderCacheSection(t){new h.Setting(t).setName(this.tr("cacheHeader")).setHeading(),new h.Setting(t).setName(this.tr("settingMaxCacheName")).setDesc(this.tr("settingMaxCacheDesc")).addText(r=>{r.setValue(String(this.plugin.settings.maxCacheEntries||J));let i=async()=>{let a=parseInt(r.getValue(),10);if(Number.isFinite(a)&&a>0){this.plugin.settings.maxCacheEntries=a,await this.plugin.saveSettings();let s=await this.plugin.pruneCacheIfNeeded();s.length>0&&new h.Notice(this.tr("cachePruned",{count:s.length})),this.display()}};return r.inputEl.addEventListener("change",()=>{i().catch(a=>console.error("[parallel-reader] failed to save cache settings",a))}),r.inputEl.addEventListener("keydown",a=>{a.key==="Enter"&&r.inputEl.blur()}),r});let e=Object.keys(this.plugin.cache).length;new h.Setting(t).setName(this.tr("cachedNotesName",{count:e})).setDesc(this.tr("cachedNotesDesc")).addButton(r=>r.setButtonText(this.tr("clearAllCacheButton")).setWarning().onClick(async()=>{let i=Object.keys(this.plugin.cache).length;await this.plugin.cacheClear(),new h.Notice(this.tr("cacheClearedAll",{count:i})),this.display()}))}};function pt(n){return String(n||"").split("/").map(t=>t.trim()).filter(t=>!!t&&t!==".."&&t!==".").join("/")}function ht(n){let t=pt(n);if(!t)return[];let e=t.split("/");return e.map((r,i)=>e.slice(0,i+1).join("/"))}async function Ht(n,t){for(let e of ht(t))if(!n.vault.getAbstractFileByPath(e))try{await n.vault.createFolder(e)}catch(r){if(!n.vault.getAbstractFileByPath(e))throw r}}var x=require("obsidian");var V="parallel-reader-view",Re=class extends x.ItemView{constructor(e,r){super(e);this.stale=!1;this.loadingMessage="";this.errorMessage="";this.plugin=r,this.sections=[],this.sourceFile=null,this.cards=[],this.activeIdx=-1}getViewType(){return V}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",r=>this.handleKeydown(r)),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 r=e.createDiv({cls:"parallel-reader-empty"});r.createEl("h3",{text:this.plugin.t("appTitle")}),r.createEl("p",{text:this.plugin.t("emptyOpenNote")}),r.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,r,i){this.sourceFile=e,this.sections=r,this.stale=!!i,this.loadingMessage="",this.errorMessage="",this.render()}renderLoading(e,r){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage=r||this.plugin.t("loadingDefault"),this.errorMessage="",this.render()}renderStreamingPreview(e,r){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=r.slice(-2e3));let b=a.querySelector(".parallel-reader-stream-counter");b&&(b.textContent=`${r.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"});I(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 d=c.createEl("div",{cls:"parallel-reader-state-title"});d.createSpan({text:this.plugin.t("loadingGenerating")+" "}),d.createSpan({cls:"parallel-reader-stream-counter",text:`${r.length} chars`});let u=c.createEl("pre",{cls:"parallel-reader-stream-text"});u.textContent=r.slice(-2e3)}renderError(e,r){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage=r||this.plugin.t("errorTitle"),this.render()}renderEmptyWithHint(e){this.sourceFile=e,this.sections=[],this.stale=!1,this.loadingMessage="",this.errorMessage="";let r=this.containerEl.children[1];r.empty();let i=r.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")}),E(i,null,this.plugin.t("actionGenerate"),()=>{this.plugin.isGeneratingFile(e)||this.plugin.runForFile(e,!1)})}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)?I(a,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(this.sourceFile)}):I(a,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0)),I(a,"copy",this.plugin.t("actionCopyAll"),()=>this.plugin.copyCurrentViewMarkdown()),I(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")}),E(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 parallel-reader-selectable"});let c=l.createDiv({cls:"parallel-reader-error-actions"});E(c,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-text-button"),E(c,"copy",this.plugin.t("actionCopyError"),()=>D(this.errorMessage,this.plugin.t("actionCopyError")),"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)=>{this.cards.push(this.renderCard(s,l,c,o))}),this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].addClass("is-active")}renderCard(e,r,i,a){let s=e.createDiv({cls:"parallel-reader-card"});s.dataset.idx=String(i),r.startLine<0&&s.addClass("parallel-reader-card-unanchored");let o=s.createEl("div",{cls:"parallel-reader-card-title"});if(o.createSpan({text:r.title}),r.startLine<0&&o.createEl("span",{text:" \u26A0",cls:"parallel-reader-warn",title:this.plugin.t("anchorMismatch")}),r.gist){let c=s.createEl("div",{cls:"parallel-reader-gist"});x.MarkdownRenderer.render(this.app,r.gist,c,a,this).catch(()=>{c.setText(r.gist)})}let l=r.bullets||[];if(l.length>0){let c=s.createEl("div",{cls:"parallel-reader-bullets-md"}),d=l.map(u=>`- ${u}`).join(` +`);x.MarkdownRenderer.render(this.app,d,c,a,this).catch(()=>{c.setText(d)})}else r.gist||s.createEl("div",{cls:"parallel-reader-empty-li",text:this.plugin.t("emptyCard")});return s.addEventListener("click",c=>{let d=window.getSelection();if(d&&d.toString().length>0)return;let u=c.target;u&&u.tagName==="A"||r.startLine>=0&&this.plugin.scrollEditorToLine(r.startLine,this.sourceFile)}),s.addEventListener("contextmenu",c=>{c.preventDefault(),this.showCardContextMenu(c,r,i)}),s}showCardContextMenu(e,r,i){let a=new x.Menu;a.addItem(s=>s.setTitle(this.plugin.t("menuCopyMarkdown")).setIcon("copy").onClick(()=>D(lt(r),this.plugin.t("copiedMarkdown")))),a.addItem(s=>s.setTitle(this.plugin.t("menuCopyPlain")).setIcon("clipboard-copy").onClick(()=>D(Gt(r),this.plugin.t("copiedPlain")))),r.anchor&&a.addItem(s=>s.setTitle(this.plugin.t("menuCopyAnchor")).setIcon("quote-glyph").onClick(()=>D(r.anchor,this.plugin.t("copiedAnchor")))),a.addSeparator(),r.startLine>=0&&a.addItem(s=>s.setTitle(this.plugin.t("menuJumpSource")).setIcon("arrow-right").onClick(()=>this.plugin.scrollEditorToLine(r.startLine,this.sourceFile))),a.addSeparator(),a.addItem(s=>s.setTitle(this.plugin.t("menuEditCard")).setIcon("pencil").onClick(()=>this.openEditCardModal(i))),a.addItem(s=>s.setTitle(this.plugin.t("menuDeleteCard")).setIcon("trash").onClick(()=>this.deleteCard(i))),a.showAtMouseEvent(e)}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 r=ke(this.activeIdx,this.sections.length,e);return this.setActiveSection(r),this.focusSummaryPane(),r}jumpToActiveSection(){let e=Fe(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 r=ye(this.sections,e);if(r.length===this.sections.length)return!1;let i=this.sections.length;return this.sections=r,this.activeIdx=be(e,i,this.activeIdx),await this.plugin.cacheReplaceCards(this.sourceFile.path,r),this.render(),new x.Notice(this.plugin.t("cardDeleted")),!0}openEditCardModal(e){return!this.sourceFile||!this.sections[e]?!1:(new Te(this.app,this.plugin,this.sections[e],async r=>{await this.updateCard(e,r)}).open(),!0)}async updateCard(e,r){if(!this.sourceFile)return!1;let i=Ce(this.sections,e,r);return i.length!==this.sections.length?!1:(this.sections=i,await this.plugin.cacheReplaceCards(this.sourceFile.path,i),this.render(),new x.Notice(this.plugin.t("cardSaved")),!0)}async exportToVault(){if(!this.sourceFile)return;let e=pt(this.plugin.settings.exportFolder),r=`${this.sourceFile.basename} - ${this.plugin.t("displayName")}.md`,i=`${e}/${r}`,a=["---",`source: [[${this.sourceFile.basename}]]`,`generated: ${new Date().toISOString().slice(0,10)}`,"tool: parallel-reader","---","",oe(`${this.sourceFile.basename} \xB7 ${this.plugin.t("displayName")}`,this.sections),""].join(` +`),s=this.plugin.app;await Ht(s,e);let o=s.vault.getAbstractFileByPath(i);o instanceof x.TFile?await s.vault.modify(o,a):await s.vault.create(i,a),new x.Notice(this.plugin.t("exported",{path:i}))}};var mt=class extends p.Plugin{constructor(){super(...arguments);this.activeBatch=null;this._scrollDispose=null;this._settingsSaveTimer=null}get cache(){return this.cacheManager.cache}t(e,r){return y(this.settings||m,e,r)}async onload(){await this.loadSettings(),this.jobs=new te,this.addRibbonIcon("book-open",this.t("ribbonOpen"),async()=>{let e=this.getActiveView();await this.ensureView()&&e?.file&&await this.syncViewToFile(e.file)}),this.registerView(V,e=>new Re(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&&await 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){new p.Notice(this.t("noCurrentNote"));return}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 Ee(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).catch(r=>console.error("[parallel-reader] sync view",r))})),this.registerEvent(this.app.workspace.on("file-menu",(e,r)=>this.addFileMenuItems(e,r))),this.registerEvent(this.app.vault.on("rename",(e,r)=>{e instanceof p.TFile&&this.handleFileRename(e,r).catch(i=>console.error("[parallel-reader] file rename",i))})),this.registerEvent(this.app.vault.on("delete",e=>{e instanceof p.TFile&&this.handleFileDelete(e).catch(r=>console.error("[parallel-reader] file delete",r))})),this.bindScrollSync()}onunload(){this.flushSettingsSave().catch(e=>console.error("[parallel-reader] flush settings on unload",e)),this.flushCacheSave().catch(e=>console.error("[parallel-reader] flush cache on unload",e))}async loadSettings(){let r=(await this.loadData()||{}).settings||{};this.settings=de(Object.assign({},m,r)),this.cacheManager=new ee(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(r=>console.error("[parallel-reader] failed to save settings",r))},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,r){return this.cacheManager.replaceCards(e,r)}async cacheClear(){return this.cacheManager.clear()}async pruneCacheIfNeeded(){return this.cacheManager.pruneIfNeeded()}async ensureView(){let{workspace:e}=this.app,r=e.getLeavesOfType(V)[0];if(!r){let i=e.getRightLeaf(!1);if(!i)return new p.Notice(this.t("viewOpenFailed")),null;r=i,await r.setViewState({type:V,active:!0})}return await e.revealLeaf(r),r.view}getActiveView(){return this.app.workspace.getActiveViewOfType(p.MarkdownView)}getParallelView(){return this.app.workspace.getLeavesOfType(V)[0]?.view}moveActiveCard(e){let r=this.getParallelView();return r?.sections.length?r.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 r=this.jobs.get(e.path),i=ot(this.settings,r),a=this.jobs.cancel(e.path);return a&&this.activeBatch?.currentPath===e.path&&me(this.activeBatch),new p.Notice(a?this.t(i):this.t("noCancelableJob")),a}requestBatchCancellation(){return me(this.activeBatch)}viewIsShowingFile(e,r){return!!e&&!!r&&e.sourceFile?.path===r.path}activeFileStillMatches(e){let r=this.getActiveView();return!r?.file||r.file.path===e.path}cancelActiveGeneration(){if(this.requestBatchCancellation()){let i=this.activeBatch?.currentPath;i&&this.jobs.cancel(i),new p.Notice(this.t("batchCancelRequested"));return}let e=this.getActiveView();if(e?.file&&this.cancelGenerationForFile(e.file))return;let r=this.getParallelView();r?.sourceFile?this.cancelGenerationForFile(r.sourceFile):new p.Notice(this.t("noCancelableJob"))}confirmRegenerateEditedCards(){return Ut(this.app,this.t("displayName"),this.t("confirmRegenerateEditedCards"))}addFileMenuItems(e,r){!(r instanceof p.TFile)||!r.path.endsWith(".md")||(e.addSeparator(),e.addItem(i=>i.setTitle(this.t("fileMenuGenerate")).setIcon("book-open").onClick(()=>this.runForFile(r,!1))),e.addItem(i=>i.setTitle(this.t("fileMenuRegen")).setIcon("refresh-cw").onClick(()=>this.runForFile(r,!0))),this.cacheManager.get(r.path)&&e.addItem(i=>i.setTitle(this.t("fileMenuClear")).setIcon("trash").onClick(async()=>{await this.cacheManager.delete(r.path),new p.Notice(this.t("cacheClearedFile",{name:r.basename}))})))}async handleFileRename(e,r){if(!(e instanceof p.TFile)||!r)return;let i=r.endsWith(".md"),a=e.path.endsWith(".md");if(!i&&!a)return;if(i&&!a){await this.cacheManager.delete(r);let o=this.getParallelView();o&&o.sourceFile?.path===r&&o.renderEmpty();return}if(!i)return;await this.cacheManager.move(r,e.path);let s=this.getParallelView();s?.sourceFile&&(s.sourceFile.path===r||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 r=this.getParallelView();r?.sourceFile?.path===e.path&&r.renderEmpty()}async exportCurrentView(){let e=this.getActiveView(),r=await this.ensureView();if(r){if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new p.Notice(this.t("noExportContent"));return}await r.exportToVault()}}async copyCurrentViewMarkdown(){let e=this.getActiveView(),r=await this.ensureView();if(r){if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new p.Notice(this.t("noCopyContent"));return}await D(oe(`${r.sourceFile.basename} \xB7 ${this.t("displayName")}`,r.sections),this.t("copiedAllMarkdown"))}}async runForActiveFile(e){let r=this.getActiveView();if(!r?.file){new p.Notice(this.t("openNoteFirst"));return}return this.runForFile(r.file,e)}async runForFile(e,r){if(!e){new p.Notice(this.t("openNoteFirst"));return}if(this.jobs.isRunning(e.path)){new p.Notice(this.t("alreadyGenerating"));return}if(Ye(this.cacheManager.get(e.path),r)&&!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"),!r){let u=this.cacheManager.get(e.path);if(u&&K(u,s,this.settings)){this.cacheTouch(e.path),this.activeFileStillMatches(e)&&i.loadFor(e,Xe(s,u.cards),!1);return}}i.renderLoading(e,this.t("loadingGenerating"));let o=Number(this.settings.maxDocChars)||m.maxDocChars;s.length>o&&new p.Notice(this.t("longNoteTruncated",{count:o})),new p.Notice(this.t("generatingNotice")),a.setPhase("generating");let l=await st(s,this.settings,a,this.streamProgressFor(i,e));if(a.throwIfCancelled(),l.length===0){new p.Notice(this.t("noCardsReturned"));return}let c=l.map(u=>({title:u.title,anchor:u.anchor,gist:u.gist,bullets:u.bullets}));a.setPhase("saving"),await this.cacheManager.put(e.path,s,c,this.settings),a.throwIfCancelled(),this.viewIsShowingFile(i,e)&&i.loadFor(e,l,!1);let d=l.filter(u=>u.startLine<0).length;new p.Notice(this.t("generationDone",{count:l.length,suffix:d?this.t("unanchoredSuffix",{count:d}):""}))}).catch(a=>this.handleGenerationError(a,e,i))}streamProgressFor(e,r){if(e)return i=>{!i.done&&this.viewIsShowingFile(e,r)&&e.renderStreamingPreview(r,i.accumulated)}}handleGenerationError(e,r,i){if(e instanceof $){new p.Notice(e.message);return}if(e instanceof k){i&&this.viewIsShowingFile(i,r)&&i.renderError(r,this.t("cancelledError")),new p.Notice(this.t("cancelled"));return}let a=Qe(e),s=e instanceof Error?e.message:String(e);console.error(e),i&&this.viewIsShowingFile(i,r)&&i.renderError(r,s),new p.Notice(this.t("generationFailed",{kind:a==="unknown"?"":` (${a})`,error:s}))}async runBatchForFolder(){if(this.activeBatch){new p.Notice(this.t("batchAlreadyRunning"));return}let e=await At(this.app,this.t("batchSelectFolder"),this.t("batchFolderPrompt"));if(e===null)return;let r=Ge(e,l=>{let c=this.app.vault.getAbstractFileByPath(l);return!!c&&!(c instanceof p.TFile)});if(!r.valid){let l=r.reason==="unsafe"?"batchInvalidFolderInput":"batchFolderNotFound";new p.Notice(this.t(l,{path:r.folderPath||e}));return}let i=Ue(this.app.vault.getMarkdownFiles(),r.folderPath);if(i.length===0){new p.Notice(this.t("batchNoMarkdown"));return}let a=ze();this.activeBatch=a;let s=He(i.length);try{for(let l=0;lthis.handleEditorScroll(e));a.addEventListener("scroll",s,{passive:!0}),this._scrollDispose=()=>{s.cancel(),a.removeEventListener("scroll",s)}}handleEditorScroll(e){let r=this.getParallelView();if(!r||!e.file||r.sourceFile?.path!==e.file.path)return;let i=e.editor,a=i&&i.cm;if(!a?.scrollDOM)return;let s=a.scrollDOM.getBoundingClientRect(),o=ut(s),l=0;try{let d=a.posAtCoords({x:s.left+20,y:o});d!=null&&(l=a.state.doc.lineAt(d).number-1)}catch{return}let c=-1;for(let d=0;d> = { + zh: { + appTitle: '对照阅读笔记', + settingsTitle: 'Parallel Reader 设置', + emptyOpenNote: '打开一篇笔记,然后运行命令:', + emptyNoCache: '该笔记尚无对照笔记缓存。运行命令:', + commandGenerate: 'Parallel Reader: 为当前笔记生成对照笔记', + displayName: '对照阅读笔记', + ribbonOpen: '打开对照笔记面板', + cmdRun: '为当前笔记生成对照笔记(缓存优先)', + cmdRegen: '强制重新生成(绕过缓存)', + cmdOpenView: '打开对照笔记面板', + cmdExport: '导出当前对照笔记到 Vault', + cmdCopyMarkdown: '复制当前对照笔记 Markdown', + cmdCancel: '取消当前对照笔记生成', + cmdClearCurrent: '清除当前笔记的缓存', + cmdClearAll: '清除所有缓存', + cmdCardPrev: '聚焦上一张摘要卡片', + cmdCardNext: '聚焦下一张摘要卡片', + cmdCardJump: '跳转到当前摘要卡片原文', + actionCancel: '取消生成', + actionRegenerate: '重新生成', + actionCopyAll: '复制全部 Markdown', + actionExport: '导出到 Vault', + staleBanner: '源笔记或生成配置已修改,当前是旧缓存。', + loadingDefault: '正在生成对照笔记...', + loadingGenerating: '对照阅读:让 LLM 读全文并自适应切段...', + loadingSubtitle: '可以继续阅读原文,生成完成后会自动刷新右侧卡片。', + errorTitle: '生成失败', + actionCopyError: '复制错误信息', + emptyCard: '(未生成)', + anchorMismatch: 'anchor 匹配失败,无法滚动联动', + menuCopyMarkdown: '复制 Markdown', + menuCopyPlain: '复制纯文本', + menuCopyAnchor: '复制 anchor 引用', + menuJumpSource: '跳转到原文', + menuEditCard: '编辑此卡片', + menuDeleteCard: '删除此卡片', + copiedMarkdown: '已复制 Markdown', + copiedPlain: '已复制纯文本', + copiedAnchor: '已复制引用原文', + copiedAllMarkdown: '已复制全部 Markdown', + copyFailed: '复制失败:{error}', + actionFailed: '{label}失败:{error}', + exported: '已导出 → {path}', + noCurrentNote: '没有当前笔记', + cacheClearedFile: '已清除缓存:{name}', + cacheClearedAll: '已清除 {count} 条缓存', + noCancelableJob: '当前没有可取消的生成任务', + cancelRequested: '已请求取消生成', + cancelRequestedApiInFlight: '已请求取消生成;当前 API 请求无法立即中断,返回后会丢弃结果。', + actionGenerate: '生成对照笔记', + fileMenuGenerate: '生成对照笔记', + fileMenuRegen: '强制重新生成对照笔记', + fileMenuClear: '清除对照笔记缓存', + noExportContent: '当前没有可导出的对照笔记', + noCopyContent: '当前没有可复制的对照笔记', + noActiveCard: '当前没有可跳转的摘要卡片', + viewOpenFailed: '无法打开对照笔记面板', + confirmRegenerateEditedCards: '这篇笔记的对照卡片已被手动编辑。重新生成会覆盖这些修改,是否继续?', + regenerateCancelled: '已取消重新生成', + cardDeleted: '已删除此卡片', + cardSaved: '已保存此卡片', + editCardTitle: '编辑摘要卡片', + editCardTitleField: '标题', + editCardGistField: '领读', + editCardBulletsField: '要点(每行一条)', + editCardCancel: '取消', + editCardSave: '保存', + openNoteFirst: '先打开一篇笔记', + alreadyGenerating: '该笔记正在生成对照笔记', + emptyNote: '笔记为空', + longNoteTruncated: '笔记较长:仅发送前 {count} 个字符给模型', + generatingNotice: '对照阅读:让 LLM 读全文并自适应切段…', + noCardsReturned: 'LLM 未返回任何 card', + generationDone: '对照笔记生成完成:{count} 段{suffix}', + unanchoredSuffix: '(⚠ {count} 段 anchor 未匹配)', + cancelled: '已取消生成', + cancelledError: '生成已取消', + generationFailed: '生成失败{kind}:{error}', + errorCustomProviderBaseUrlRequired: '自定义 provider 需要填写 API Base URL。', + errorApiBaseUrlMissing: 'API Base URL 未设置。请在设置里选择 provider 或填写自定义 base URL。', + errorModelMissing: 'Model 未设置。请在设置里填写模型 ID。', + errorApiKeyMissing: 'API key 未设置。请在设置里填写 API Key{hint}。', + errorApiKeyEnvHint: ' 或环境变量 {envVar}', + errorLlmNonJson: 'LLM 返回非 JSON:\n{excerpt}', + errorCustomHeadersJsonParse: '自定义 headers JSON 解析失败:{error}', + errorCustomHeadersJsonObject: '自定义 headers JSON 必须是对象', + errorCustomHeadersLineFormat: '自定义 headers 每行格式应为 `Header-Name: value`', + errorProviderNonJson: '{label} 返回非 JSON:\n{excerpt}', + errorProviderRequestFailed: '{label} 请求失败:{error}', + errorProviderApiStatus: '{label} API 返回 HTTP {status}:{excerpt}', + noEditor: '找不到源笔记对应的编辑器窗口', + settingUiLanguageName: '界面语言', + settingUiLanguageDesc: '控制插件界面、命令和提示文案;Auto 跟随 Obsidian/系统语言', + settingBackendName: 'Backend', + settingBackendDesc: '生成 bullet 的后端:CLI 复用本机登录;API 支持 OpenAI/Anthropic/Gemini 及兼容代理', + settingCliPathName: 'CLI 路径(可选)', + settingCliPathDesc: '留空则自动探测常见位置;Obsidian GUI 启动时不继承 shell PATH,必要时填绝对路径', + settingCliPathPlaceholder: '例:/Users/you/bin/codex', + settingCliTimeoutName: 'CLI 超时 (ms)', + apiProviderHeader: 'API Provider', + settingProviderPresetName: 'Provider preset', + settingProviderPresetDesc: '参考 OpenClaw 的 provider/model 思路:preset 只负责协议、base URL 和认证默认值', + settingApiFormatName: 'API format', + settingApiFormatDesc: '不同 provider 的 wire protocol;OpenAI-compatible 代理通常选 Chat Completions', + settingBaseUrlName: 'Base URL', + settingBaseUrlDesc: '填 provider 根地址,不要附加 /chat/completions;留空时使用 preset 默认值', + settingApiKeyName: 'API Key', + settingApiKeyDesc: '本地 Ollama/LM Studio 可留空;其他 provider 通常需要 key', + settingApiKeyEnvName: 'API Key 环境变量', + settingApiKeyEnvDesc: '可选;Obsidian GUI 不一定继承 shell 环境,直接填 API Key 更稳定', + settingAuthTypeName: '认证方式', + settingAuthTypeDesc: 'Auto 使用 provider preset;自定义代理可按需要改成 Bearer、x-api-key 或 none', + settingHeadersName: '额外 headers', + settingHeadersDesc: '可选。支持 JSON 对象或每行 `Header: value`,用于 Cloudflare AI Gateway 等代理', + settingMaxTokensName: '最大输出 tokens', + settingModelName: 'Model', + settingModelDescApi: + 'API 调用的模型 ID;支持 OpenClaw 风格 provider/model,若 provider 前缀匹配当前 preset 会自动剥离', + settingModelDescCli: 'Claude Code 下会传 --model;Codex 下通常忽略(用 Codex 默认配置)', + settingMaxInputName: '最大输入字符数', + settingMaxInputDesc: '超过该长度会截断后再发送给模型;长上下文模型可适当调大', + promptHeader: 'Prompt', + settingPromptLanguageName: '输出语言', + settingPromptLanguageDesc: '控制 title/gist/bullets 的语言;anchor 始终逐字复制原文', + settingCardRangeName: '卡片数量范围', + settingCardRangeDesc: '模型会在这个范围内自适应切段', + settingCustomPromptName: '自定义 system prompt', + settingCustomPromptDesc: '可选。支持变量:{minCards}、{maxCards}、{languageInstruction}、{schema}、{example}', + settingCustomPromptPlaceholder: '留空使用内置 prompt', + settingTestBackendName: '测试当前后端', + settingTestBackendDescApi: '会发起一次最小 LLM 请求验证 API 设置', + settingTestBackendDescCli: '调用 ` --version` 验证 spawn 能找到二进制', + backendTestFailed: '✗ 后端测试失败:{error}', + settingExportFolderName: '导出文件夹', + settingExportFolderDesc: '对照笔记生成位置(相对 Vault 根)', + cacheHeader: '缓存', + settingMaxCacheName: '最大缓存篇数', + settingMaxCacheDesc: '超过上限后按最近访问时间淘汰最旧的笔记缓存;缓存保存在插件目录的 cache.json', + cachePruned: '已淘汰 {count} 条旧缓存', + cachedNotesName: '已缓存笔记:{count} 篇', + cachedNotesDesc: '缓存以源笔记 SHA1 + 生成配置指纹作为失效键,源笔记或模型配置修改后会显示 stale 提示', + clearAllCacheButton: '清除所有缓存', + settingStreamingName: '流式输出', + settingStreamingDesc: '启用后生成时实时显示 LLM 输出进度(仅 OpenAI Chat 和 Anthropic 格式支持)', + settingStreamingTimeoutName: '流式超时 (ms)', + settingStreamingTimeoutDesc: '单次流式请求的超时时间;低于 1000ms 或非法值会恢复默认值', + cmdBatchGenerate: '批量生成对照笔记(当前文件夹)', + batchSelectFolder: '请输入要批量处理的文件夹路径(留空为 Vault 根目录)', + batchFolderPrompt: '文件夹路径', + batchAlreadyRunning: '批量生成正在运行', + batchCancelRequested: '已请求取消批量生成;当前文件结束后停止', + batchInvalidFolderInput: '文件夹路径无效:{path}', + batchFolderNotFound: '文件夹不存在:{path}', + batchNoMarkdown: '该文件夹下没有 Markdown 文件', + batchProgress: '批量生成:{current}/{total}', + batchDone: '批量生成完成:共处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇', + batchCancelled: '批量生成已取消:已处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇', + }, + 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', + actionCopyError: 'Copy error', + 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 → {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.', + actionGenerate: 'Generate parallel notes', + 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: ' (⚠ {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:\n{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:\n{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: '✗ 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)', + settingStreamingTimeoutName: 'Streaming timeout (ms)', + settingStreamingTimeoutDesc: + 'Timeout for one streaming request; invalid values or values below 1000ms use the default', + 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', + batchInvalidFolderInput: 'Invalid folder path: {path}', + batchFolderNotFound: 'Folder not found: {path}', + batchNoMarkdown: 'No Markdown files found in that folder', + batchProgress: 'Batch generating: {current}/{total}', + batchDone: 'Batch complete: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}', + batchCancelled: 'Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}', + }, +}; diff --git a/src/i18n.ts b/src/i18n.ts index 5560734..8ee4675 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,335 +1,9 @@ 'use strict'; +import { STRINGS } from './i18n-strings'; import type { PluginSettings } from './types'; -export const STRINGS: Record> = { - zh: { - appTitle: '对照阅读笔记', - settingsTitle: 'Parallel Reader 设置', - emptyOpenNote: '打开一篇笔记,然后运行命令:', - emptyNoCache: '该笔记尚无对照笔记缓存。运行命令:', - commandGenerate: 'Parallel Reader: 为当前笔记生成对照笔记', - displayName: '对照阅读笔记', - ribbonOpen: '打开对照笔记面板', - cmdRun: '为当前笔记生成对照笔记(缓存优先)', - cmdRegen: '强制重新生成(绕过缓存)', - cmdOpenView: '打开对照笔记面板', - cmdExport: '导出当前对照笔记到 Vault', - cmdCopyMarkdown: '复制当前对照笔记 Markdown', - cmdCancel: '取消当前对照笔记生成', - cmdClearCurrent: '清除当前笔记的缓存', - cmdClearAll: '清除所有缓存', - cmdCardPrev: '聚焦上一张摘要卡片', - cmdCardNext: '聚焦下一张摘要卡片', - cmdCardJump: '跳转到当前摘要卡片原文', - actionCancel: '取消生成', - actionRegenerate: '重新生成', - actionCopyAll: '复制全部 Markdown', - actionExport: '导出到 Vault', - staleBanner: '源笔记或生成配置已修改,当前是旧缓存。', - loadingDefault: '正在生成对照笔记...', - loadingGenerating: '对照阅读:让 LLM 读全文并自适应切段...', - loadingSubtitle: '可以继续阅读原文,生成完成后会自动刷新右侧卡片。', - errorTitle: '生成失败', - actionCopyError: '复制错误信息', - emptyCard: '(未生成)', - anchorMismatch: 'anchor 匹配失败,无法滚动联动', - menuCopyMarkdown: '复制 Markdown', - menuCopyPlain: '复制纯文本', - menuCopyAnchor: '复制 anchor 引用', - menuJumpSource: '跳转到原文', - menuEditCard: '编辑此卡片', - menuDeleteCard: '删除此卡片', - copiedMarkdown: '已复制 Markdown', - copiedPlain: '已复制纯文本', - copiedAnchor: '已复制引用原文', - copiedAllMarkdown: '已复制全部 Markdown', - copyFailed: '复制失败:{error}', - actionFailed: '{label}失败:{error}', - exported: '已导出 → {path}', - noCurrentNote: '没有当前笔记', - cacheClearedFile: '已清除缓存:{name}', - cacheClearedAll: '已清除 {count} 条缓存', - noCancelableJob: '当前没有可取消的生成任务', - cancelRequested: '已请求取消生成', - cancelRequestedApiInFlight: '已请求取消生成;当前 API 请求无法立即中断,返回后会丢弃结果。', - actionGenerate: '生成对照笔记', - fileMenuGenerate: '生成对照笔记', - fileMenuRegen: '强制重新生成对照笔记', - fileMenuClear: '清除对照笔记缓存', - noExportContent: '当前没有可导出的对照笔记', - noCopyContent: '当前没有可复制的对照笔记', - noActiveCard: '当前没有可跳转的摘要卡片', - viewOpenFailed: '无法打开对照笔记面板', - confirmRegenerateEditedCards: '这篇笔记的对照卡片已被手动编辑。重新生成会覆盖这些修改,是否继续?', - regenerateCancelled: '已取消重新生成', - cardDeleted: '已删除此卡片', - cardSaved: '已保存此卡片', - editCardTitle: '编辑摘要卡片', - editCardTitleField: '标题', - editCardGistField: '领读', - editCardBulletsField: '要点(每行一条)', - editCardCancel: '取消', - editCardSave: '保存', - openNoteFirst: '先打开一篇笔记', - alreadyGenerating: '该笔记正在生成对照笔记', - emptyNote: '笔记为空', - longNoteTruncated: '笔记较长:仅发送前 {count} 个字符给模型', - generatingNotice: '对照阅读:让 LLM 读全文并自适应切段…', - noCardsReturned: 'LLM 未返回任何 card', - generationDone: '对照笔记生成完成:{count} 段{suffix}', - unanchoredSuffix: '(⚠ {count} 段 anchor 未匹配)', - cancelled: '已取消生成', - cancelledError: '生成已取消', - generationFailed: '生成失败{kind}:{error}', - errorCustomProviderBaseUrlRequired: '自定义 provider 需要填写 API Base URL。', - errorApiBaseUrlMissing: 'API Base URL 未设置。请在设置里选择 provider 或填写自定义 base URL。', - errorModelMissing: 'Model 未设置。请在设置里填写模型 ID。', - errorApiKeyMissing: 'API key 未设置。请在设置里填写 API Key{hint}。', - errorApiKeyEnvHint: ' 或环境变量 {envVar}', - errorLlmNonJson: 'LLM 返回非 JSON:\n{excerpt}', - errorCustomHeadersJsonParse: '自定义 headers JSON 解析失败:{error}', - errorCustomHeadersJsonObject: '自定义 headers JSON 必须是对象', - errorCustomHeadersLineFormat: '自定义 headers 每行格式应为 `Header-Name: value`', - errorProviderNonJson: '{label} 返回非 JSON:\n{excerpt}', - errorProviderRequestFailed: '{label} 请求失败:{error}', - errorProviderApiStatus: '{label} API 返回 HTTP {status}:{excerpt}', - noEditor: '找不到源笔记对应的编辑器窗口', - settingUiLanguageName: '界面语言', - settingUiLanguageDesc: '控制插件界面、命令和提示文案;Auto 跟随 Obsidian/系统语言', - settingBackendName: 'Backend', - settingBackendDesc: '生成 bullet 的后端:CLI 复用本机登录;API 支持 OpenAI/Anthropic/Gemini 及兼容代理', - settingCliPathName: 'CLI 路径(可选)', - settingCliPathDesc: '留空则自动探测常见位置;Obsidian GUI 启动时不继承 shell PATH,必要时填绝对路径', - settingCliPathPlaceholder: '例:/Users/you/bin/codex', - settingCliTimeoutName: 'CLI 超时 (ms)', - apiProviderHeader: 'API Provider', - settingProviderPresetName: 'Provider preset', - settingProviderPresetDesc: '参考 OpenClaw 的 provider/model 思路:preset 只负责协议、base URL 和认证默认值', - settingApiFormatName: 'API format', - settingApiFormatDesc: '不同 provider 的 wire protocol;OpenAI-compatible 代理通常选 Chat Completions', - settingBaseUrlName: 'Base URL', - settingBaseUrlDesc: '填 provider 根地址,不要附加 /chat/completions;留空时使用 preset 默认值', - settingApiKeyName: 'API Key', - settingApiKeyDesc: '本地 Ollama/LM Studio 可留空;其他 provider 通常需要 key', - settingApiKeyEnvName: 'API Key 环境变量', - settingApiKeyEnvDesc: '可选;Obsidian GUI 不一定继承 shell 环境,直接填 API Key 更稳定', - settingAuthTypeName: '认证方式', - settingAuthTypeDesc: 'Auto 使用 provider preset;自定义代理可按需要改成 Bearer、x-api-key 或 none', - settingHeadersName: '额外 headers', - settingHeadersDesc: '可选。支持 JSON 对象或每行 `Header: value`,用于 Cloudflare AI Gateway 等代理', - settingMaxTokensName: '最大输出 tokens', - settingModelName: 'Model', - settingModelDescApi: - 'API 调用的模型 ID;支持 OpenClaw 风格 provider/model,若 provider 前缀匹配当前 preset 会自动剥离', - settingModelDescCli: 'Claude Code 下会传 --model;Codex 下通常忽略(用 Codex 默认配置)', - settingMaxInputName: '最大输入字符数', - settingMaxInputDesc: '超过该长度会截断后再发送给模型;长上下文模型可适当调大', - promptHeader: 'Prompt', - settingPromptLanguageName: '输出语言', - settingPromptLanguageDesc: '控制 title/gist/bullets 的语言;anchor 始终逐字复制原文', - settingCardRangeName: '卡片数量范围', - settingCardRangeDesc: '模型会在这个范围内自适应切段', - settingCustomPromptName: '自定义 system prompt', - settingCustomPromptDesc: '可选。支持变量:{minCards}、{maxCards}、{languageInstruction}、{schema}、{example}', - settingCustomPromptPlaceholder: '留空使用内置 prompt', - settingTestBackendName: '测试当前后端', - settingTestBackendDescApi: '会发起一次最小 LLM 请求验证 API 设置', - settingTestBackendDescCli: '调用 ` --version` 验证 spawn 能找到二进制', - backendTestFailed: '✗ 后端测试失败:{error}', - settingExportFolderName: '导出文件夹', - settingExportFolderDesc: '对照笔记生成位置(相对 Vault 根)', - cacheHeader: '缓存', - settingMaxCacheName: '最大缓存篇数', - settingMaxCacheDesc: '超过上限后按最近访问时间淘汰最旧的笔记缓存;缓存保存在插件目录的 cache.json', - cachePruned: '已淘汰 {count} 条旧缓存', - cachedNotesName: '已缓存笔记:{count} 篇', - cachedNotesDesc: '缓存以源笔记 SHA1 + 生成配置指纹作为失效键,源笔记或模型配置修改后会显示 stale 提示', - clearAllCacheButton: '清除所有缓存', - settingStreamingName: '流式输出', - settingStreamingDesc: '启用后生成时实时显示 LLM 输出进度(仅 OpenAI Chat 和 Anthropic 格式支持)', - settingStreamingTimeoutName: '流式超时 (ms)', - settingStreamingTimeoutDesc: '单次流式请求的超时时间;低于 1000ms 或非法值会恢复默认值', - cmdBatchGenerate: '批量生成对照笔记(当前文件夹)', - batchSelectFolder: '请输入要批量处理的文件夹路径(留空为 Vault 根目录)', - batchFolderPrompt: '文件夹路径', - batchAlreadyRunning: '批量生成正在运行', - batchCancelRequested: '已请求取消批量生成;当前文件结束后停止', - batchInvalidFolderInput: '文件夹路径无效:{path}', - batchFolderNotFound: '文件夹不存在:{path}', - batchNoMarkdown: '该文件夹下没有 Markdown 文件', - batchProgress: '批量生成:{current}/{total}', - batchDone: '批量生成完成:共处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇', - batchCancelled: '批量生成已取消:已处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇,失败 {errors} 篇', - }, - 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', - actionCopyError: 'Copy error', - 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 → {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.', - actionGenerate: 'Generate parallel notes', - 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: ' (⚠ {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:\n{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:\n{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: '✗ 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)', - settingStreamingTimeoutName: 'Streaming timeout (ms)', - settingStreamingTimeoutDesc: - 'Timeout for one streaming request; invalid values or values below 1000ms use the default', - 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', - batchInvalidFolderInput: 'Invalid folder path: {path}', - batchFolderNotFound: 'Folder not found: {path}', - batchNoMarkdown: 'No Markdown files found in that folder', - batchProgress: 'Batch generating: {current}/{total}', - batchDone: 'Batch complete: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}', - batchCancelled: 'Batch cancelled: processed {processed}/{total}, skipped {skipped} (cached), errors {errors}', - }, -}; +export { STRINGS } from './i18n-strings'; export function resolveUiLanguage(settings: Pick | null): string { const configured = settings?.uiLanguage;