diff --git a/.gitignore b/.gitignore index 06114e9..00673e8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ cache.json node_modules/ dist/ *.log + +# Agent state +.agent/ diff --git a/README.md b/README.md index fb16ffd..a6825ef 100644 --- a/README.md +++ b/README.md @@ -1,183 +1,125 @@ # Obsidian Parallel Reader -An Obsidian plugin that splits any long note into a left/right parallel-reading layout: the original content stays on the left; LLM-generated section summaries appear on the right. Scrolling the source highlights the matching summary card; clicking a summary jumps the source editor. +一个 Obsidian 插件:左边原文、右边 AI 摘要卡片,滚动联动、点击跳转。 -Inspired by the reading workflow demo in [this Bilibili video](https://www.bilibili.com/video/BV1FxoGBVETm/). +灵感来自 [这个 B 站视频](https://www.bilibili.com/video/BV1FxoGBVETm/) 的阅读工作流演示。 -## Features +## 功能 -- **Self-adaptive segmentation** — the LLM decides natural topic boundaries, no need to match markdown headings. Short adjacent sections merge; long ones split. -- **Anchor-based line resolution** — summaries carry a verbatim quote from the source; the plugin finds the line number by `indexOf` (with graceful fallbacks). -- **Scroll-sync highlighting** — scrolling the source editor updates the active card in the summary pane. -- **Right-click → copy** — Markdown / plain text / anchor quote / jump to source, as a native Obsidian context menu. -- **Markdown rendering** — summaries render through Obsidian's official `MarkdownRenderer`, so tables, bold, code, wikilinks all work. -- **Persistent bounded cache** — generated summaries are cached by SHA-1 of source content plus generation settings in a separate `cache.json`. Reopening a note shows the cached view instantly; edits or model changes invalidate the cache (shows a stale banner). The cache has an LRU-style entry limit. -- **Panel actions** — regenerate, copy all Markdown, or export the current comparison note directly from the right pane. -- **Vault lifecycle aware** — cached summaries follow Markdown file renames and are removed when the source note is deleted. -- **Configurable input limit** — default 20,000 characters, adjustable for long-context models and included in the cache fingerprint. -- **Prompt controls** — choose summary language, card count range, and an optional custom system prompt. Prompt settings are included in the cache fingerprint. -- **Localized UI** — plugin commands, settings, pane text, and notices support Chinese and English. -- **Multiple model access paths** — Claude Code CLI, Codex CLI, direct provider APIs, and OpenAI/Anthropic-compatible proxies. The API backend follows the same lightweight `provider/model + base URL + protocol` idea as OpenClaw. +- **自适应切段** — LLM 自行判断主题边界,不依赖 markdown 标题。短段合并、长段拆分。 +- **Anchor 定位** — 每张摘要卡片携带原文逐字引用,插件通过 `indexOf` 定位行号(含多级容错回退)。 +- **滚动联动** — 滚动编辑器时,右侧对应卡片自动高亮。 +- **右键菜单** — 复制 Markdown / 纯文本 / anchor 引用 / 跳转原文 / 编辑 / 删除卡片。 +- **Markdown 渲染** — 摘要通过 Obsidian 的 `MarkdownRenderer` 渲染,表格、加粗、代码、wikilink 均正常显示。 +- **流式输出** — 支持 OpenAI Chat 和 Anthropic API 的 SSE 流式响应,生成时实时显示进度。 +- **持久化缓存** — 生成结果按源文件 SHA-1 + 生成配置缓存在 `cache.json`,重新打开秒出;内容或配置变更后显示"已过期"提示。 +- **面板操作** — 重新生成、复制全部 Markdown、导出到 Vault。 +- **文件感知** — 缓存跟随文件重命名,源文件删除时自动清理。 +- **Prompt 控制** — 可选输出语言、卡片数量范围、自定义 system prompt。 +- **中英双语 UI** — 命令、设置、面板文案全部支持中文和英文。 +- **多 Provider 支持** — Anthropic、OpenAI、Gemini、OpenRouter、Groq、DeepSeek、Moonshot、千帆、MiniMax、xAI、Mistral、Cerebras、智谱、Ollama、LM Studio 及自定义兼容端点。 -## Installation +## 安装 -### Manual install (until this lands in Community Plugins) +### 手动安装 -1. Clone this repo into `.obsidian/plugins/parallel-reader/` in your vault, or symlink it there: - ```bash - ln -s /path/to/obsidian-parallel-reader /path/to/YourVault/.obsidian/plugins/parallel-reader - ``` -2. In Obsidian: **Settings → Community plugins → Installed plugins** → enable **Parallel Reader**. +1. 下载 [最新 Release](https://github.com/fancive/obsidian-parallel-reader/releases) 中的 `main.js`、`manifest.json`、`styles.css` +2. 在 Vault 的 `.obsidian/plugins/` 目录下创建 `parallel-reader` 文件夹 +3. 将三个文件放入该文件夹 +4. Obsidian → **设置 → 第三方插件 → 已安装插件** → 启用 **Parallel Reader** -### Pick a backend +### 配置 Provider -| Backend | Auth | Cost | Notes | -|---------|------|------|-------| -| **Claude Code CLI** | OAuth in macOS Keychain | Included in Claude subscription | ⚠️ Keychain ACL may block Obsidian subprocess reads — may not work from GUI | -| **Codex CLI** | File-based token (`~/.codex/auth.json`) | Included in ChatGPT Plus | ✅ Works from Obsidian GUI; recommended | -| **API / Provider** | API key or local server | Provider billing / local | Supports Anthropic, OpenAI, Gemini, OpenRouter, Groq, DeepSeek, Moonshot, Qianfan, MiniMax, xAI, Mistral, Cerebras, Z.AI, Ollama, LM Studio, and custom compatible endpoints | +在插件设置中选择一个 Provider preset,填入 API Key 和模型 ID 即可。 -### API provider mode +| Provider | API 格式 | 说明 | +|----------|----------|------| +| Anthropic | `anthropic-messages` | 默认 preset,推荐 | +| OpenAI | `openai-chat` | Chat Completions | +| Google Gemini | `google-generative-ai` | generateContent | +| OpenRouter / Groq / DeepSeek / Moonshot 等 | `openai-chat` | OpenAI 兼容格式 | +| Ollama / LM Studio | `openai-chat` | 本地模型,无需 API Key | +| 自定义端点 | 任意 | 填写 Base URL 即可 | -In plugin settings, choose **Backend → API / Provider**, then select a provider preset. Presets fill the protocol, base URL, auth header, and environment variable name; you still control the model ID. +Model ID 支持 `provider/model` 写法(如 `anthropic/claude-sonnet-4-6`),匹配当前 preset 时自动剥离前缀。 -Supported protocol formats: +## 命令 -| Format | Use it for | -|--------|------------| -| `anthropic-messages` | Anthropic API and Anthropic-compatible proxies | -| `openai-chat` | OpenAI-compatible `/chat/completions` providers and local proxies | -| `openai-responses` | OpenAI `/responses` endpoint | -| `google-generative-ai` | Gemini `generateContent` API | +| 命令 | 说明 | +|------|------| +| 为当前笔记生成对照笔记(缓存优先) | 有缓存直接显示,否则调用 LLM | +| 强制重新生成(绕过缓存) | 忽略缓存重新调用 LLM | +| 打开对照笔记面板 | 打开右侧面板 | +| 导出当前对照笔记到 Vault | 保存为 Markdown 文件 | +| 复制当前对照笔记 Markdown | 复制到剪贴板 | +| 取消当前对照笔记生成 | 取消正在进行的生成 | +| `Alt+↑` / `Alt+↓` | 在摘要卡片间切换 | +| `Enter`(在摘要面板内) | 跳转到当前卡片对应的原文位置 | -Model IDs accept OpenClaw-style `provider/model` values. If the prefix matches the selected preset, the plugin strips it before the request. For example: +## 交互 -- Preset `anthropic`, model `anthropic/claude-sonnet-4-6` → sends `claude-sonnet-4-6` -- Preset `openrouter`, model `openrouter/anthropic/claude-sonnet-4-5` → sends `anthropic/claude-sonnet-4-5` -- Preset `ollama`, model `llama3.3` → sends `llama3.3` +| 操作 | 效果 | +|------|------| +| 点击卡片 | 跳转到原文对应位置 | +| 右键卡片 | 上下文菜单 | +| 滚动编辑器 | 右侧卡片自动高亮 | +| 拖选文字 | 正常选择文本(不触发跳转) | +| 文件右键菜单 | 生成 / 重新生成 / 清除缓存 | +| Ribbon 图标 | 打开对照面板 | -For Cloudflare AI Gateway or other proxies, use **额外 headers** with either JSON: - -```json -{"cf-aig-authorization":"Bearer ..."} -``` - -or one header per line: - -```text -cf-aig-authorization: Bearer ... -``` - -### Configure the CLI path - -Obsidian launched from Finder does **not** inherit your shell `PATH`. Create a stable symlink in `~/bin/` and point the plugin setting at it: - -```bash -mkdir -p ~/bin -# Claude Code (Mach-O binary, no Node needed) -ln -sf "$(readlink -f "$(which claude)")" ~/bin/claude -# Codex (Node script — use the wrapper pattern below instead) -cat > ~/bin/codex <<'EOF' -#!/bin/bash -NODE="/path/to/your/node" -CODEX_JS="/path/to/@openai/codex/bin/codex.js" -exec "$NODE" "$CODEX_JS" "$@" -EOF -chmod +x ~/bin/codex -``` - -Then in the plugin settings, paste `/Users/you/bin/codex` (or `~/bin/claude`) into **CLI 路径**, and click **Test** to verify. - -## Commands - -| Command | What it does | -|---------|--------------| -| `为当前笔记生成对照笔记(缓存优先)` | Show cached summary if present, otherwise call LLM | -| `强制重新生成(绕过缓存)` | Re-run the LLM even if cache matches | -| `打开对照笔记面板` | Open right-pane view (loads cache if present) | -| `导出当前对照笔记到 Vault` | Save the current comparison note under the configured export folder | -| `复制当前对照笔记 Markdown` | Copy the current comparison note as Markdown | -| `取消当前对照笔记生成` | Mark the active generation job as cancelled | -| `清除当前笔记的缓存` | Drop the cache entry for the active note | -| `清除所有缓存` | Wipe all cached summaries | -| `聚焦上一张摘要卡片` / `Focus previous summary card` | Move the active summary card upward | -| `聚焦下一张摘要卡片` / `Focus next summary card` | Move the active summary card downward | -| `跳转到当前摘要卡片原文` / `Jump current summary card to source` | Jump the source editor to the active card | - -## Interaction - -| Action | Effect | -|--------|--------| -| Left-click a card body | Scroll source editor to that section | -| Right-click a card | Context menu: Copy Markdown / Copy plain text / Copy anchor / Jump / Edit / Delete card | -| Header icon buttons | Regenerate or cancel / copy all Markdown / export to Vault | -| File context menu | Generate / force regenerate / clear cache for a Markdown file | -| Ribbon icon | Open the comparison pane for the active note | -| `Alt+↑` / `Alt+↓` | Move between summary cards | -| `Enter` in the summary pane | Jump to the active card's source line | -| Drag to select text | Normal text selection (does not trigger jump) | -| Scroll source editor | Active card gets highlighted on the right | - -## Development +## 开发 ```bash npm install -npm run dev # watch main.ts + src/**/*.ts and rebuild main.js -npm run build # production bundle for Obsidian -npm run typecheck -npm test -node --check main.js +npm run dev # 监听 main.ts + src/**/*.ts,自动重新构建 +npm run build # 生产构建 +npm run typecheck # TypeScript 类型检查(strict 模式) +npm run lint # Biome lint +npm test # 构建 + 类型检查 + 测试 ``` -### Code layout +### 项目结构 -The plugin keeps `main.js` as the generated Obsidian runtime bundle. Edit `main.ts` and `src/**/*.ts`; esbuild bundles them back into root `main.js`. +| 文件 | 职责 | +|------|------| +| `main.ts` | 插件生命周期、命令注册、缓存管理、滚动联动 | +| `src/view.ts` | 右侧面板视图、卡片渲染、键盘导航、导出 | +| `src/modal.ts` | 卡片编辑弹窗 | +| `src/settings-tab.ts` | 设置面板 | +| `src/providers.ts` | API 请求/响应适配器(Anthropic、OpenAI、Gemini) | +| `src/streaming.ts` | SSE 流式解析 | +| `src/prompt.ts` | Prompt 构建、语言控制、自定义 prompt 模板 | +| `src/schema.ts` | JSON 提取、卡片数据归一化、结构化输出 schema | +| `src/anchor.ts` | Anchor 到行号的匹配(含容错) | +| `src/settings.ts` | 默认值、Provider preset、缓存指纹 | +| `src/i18n.ts` | 中英文 UI 翻译 | +| `src/cache.ts` / `src/cards.ts` / `src/navigation.ts` / `src/scroll.ts` / `src/vault.ts` / `src/markdown.ts` | 各种纯函数工具模块 | -| File | Responsibility | -|------|----------------| -| `main.ts` | Obsidian lifecycle, commands, right-pane view, settings tab orchestration | -| `src/anchor.ts` | Anchor-to-line matching and whitespace-normalized fallback | -| `src/cache.ts` | Cache entry touch semantics and compact cache-file serialization | -| `src/cards.ts` | Card list edit/delete helpers | -| `src/i18n.ts` | Chinese/English UI strings and translation helper | -| `src/navigation.ts` | Summary-card keyboard navigation helpers | -| `src/prompt.ts` | Prompt construction, language controls, and custom prompt templating | -| `src/settings.ts` | Defaults, provider presets, settings normalization, cache fingerprinting and pruning | -| `src/vault.ts` | Vault path normalization and recursive folder creation | -| `src/schema.ts` | JSON extraction, card payload normalization, structured-output schemas | -| `src/scroll.ts` | Scroll-sync requestAnimationFrame throttling helper | -| `src/providers.ts` | API provider request/response adapters | -| `src/generation-job-manager.ts` | Per-file generation state, cancellation, and error classification | -| `src/markdown.ts` | Card-to-Markdown/plain-text serialization | +## 设计 -## Design notes - -The LLM is asked to return JSON with this shape: +LLM 返回如下 JSON: ```json { "cards": [ { - "title": "3-10 character short heading", - "anchor": "40-80 characters, verbatim from source, used for line resolution", - "gist": "20-40 character one-line summary serving as a lead-in", - "bullets": ["3-6 supporting details, 20-50 chars each"] + "title": "3-10 字短标题", + "anchor": "40-80 字,从原文逐字复制,用于定位", + "gist": "20-40 字一句话领读", + "bullets": ["3-6 条支撑要点,每条 20-50 字"] } ] } ``` -- The **anchor** is the key mechanism that keeps scroll-sync working without relying on markdown headings. It's copied verbatim so `content.indexOf(anchor)` finds the origin line; graceful fallbacks (prefix trimming, whitespace normalization) handle minor LLM drift. -- The **gist + bullets** dual structure was chosen after several iterations — pure prose felt wall-of-text, pure bullets felt fragmented. A one-line lead-in before bullets gives both overview and scannable detail. -- Output is rendered via `MarkdownRenderer.render()` so any tables / bold / code / wikilinks in the LLM response render natively. +- **anchor** 是滚动联动的核心机制,通过 `content.indexOf(anchor)` 定位行号,不依赖 markdown 标题。 +- **gist + bullets** 双层结构在多次迭代后确定 — 纯散文太密、纯列表太碎,一句话导读 + bullet 兼顾概览和细节。 -## Known limitations +## 已知限制 -- **Document size cap**: defaults to 20,000 characters. Longer notes are truncated unless you raise **最大输入字符数** in settings. -- **Claude Code CLI auth**: Obsidian subprocess reads of the macOS Keychain `Claude Code-credentials` service may fail due to code-signing ACL. Codex backend doesn't have this issue. -- **No streaming**: generation is a single batch call; expect ~5-15s wait depending on document length and backend. -- **Provider feature gaps**: some OpenAI-compatible providers reject optional fields or use provider-specific model IDs. If a request fails, verify the model ID and switch between `openai-chat`, `openai-responses`, or the provider's native format. -- **Preview mode**: scroll-sync uses Obsidian's `setEphemeralState` which works in reading mode too, but accuracy depends on how CodeMirror resolves line positions. +- **文档长度**:默认 20,000 字符,超长笔记会截断(可在设置中调大)。 +- **Provider 差异**:部分 OpenAI 兼容 provider 可能不支持某些可选字段,请根据报错切换 API 格式。 +- **Preview 模式**:滚动联动在阅读模式下也能工作,但精度取决于 CodeMirror 的行位置计算。 ## License diff --git a/main.js b/main.js index db3b966..3902a38 100644 --- a/main.js +++ b/main.js @@ -1,29 +1,21 @@ 'use strict'; -"use strict";var dt=Object.create;var Z=Object.defineProperty;var pt=Object.getOwnPropertyDescriptor;var ht=Object.getOwnPropertyNames;var mt=Object.getPrototypeOf,gt=Object.prototype.hasOwnProperty;var ft=(t,n)=>{for(var e in n)Z(t,e,{get:n[e],enumerable:!0})},Ke=(t,n,e,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let a of ht(n))!gt.call(t,a)&&a!==e&&Z(t,a,{get:()=>n[a],enumerable:!(r=pt(n,a))||r.enumerable});return t};var Q=(t,n,e)=>(e=t!=null?dt(mt(t)):{},Ke(n||!t||!t.__esModule?Z(e,"default",{value:t,enumerable:!0}):e,t)),yt=t=>Ke(Z({},"__esModule",{value:!0}),t);var $t={};ft($t,{__test:()=>Kt,default:()=>Jt});module.exports=yt($t);var m=require("obsidian");function ee(t,n){if(!n)return-1;let e=p=>p.replace(/\s+/g," ").trim(),r=p=>{let h=[],f=[],u=!1;for(let C=0;C0;continue}u&&(h.push(" "),f.push(C),u=!1),h.push(Je),f.push(C)}return{text:h.join(""),map:f}},a=p=>{if(!p)return-1;let h=t.indexOf(p);if(h===-1)return-1;let f=0;for(let u=0;u=0||(i=a(n.trim()),i>=0))return i;for(let p of[60,40,25,15]){let h=n.trim().slice(0,p);if(i=a(h),i>=0)return i}let s=r(t),o=e(n).slice(0,30);if(!o)return-1;let l=s.text.indexOf(o);if(l===-1)return-1;let d=s.map[l];if(d==null)return-1;let c=0;for(let p=0;p0}function te(t,n){let e=Array.isArray(t)?t.slice():[];return!Number.isInteger(n)||n<0||n>=e.length||e.splice(n,1),e}function re(t,n,e){if(!Number.isInteger(t)||!Number.isInteger(n)||n<=0||!Number.isInteger(e)||e<0||t<0||t>=n)return e;let r=n-1;return r<=0?-1:t=r.length||(r[n]=Object.assign({},r[n],e||{})),r}var We=require("child_process"),Ye=Q(require("fs")),se=Q(require("os")),T=Q(require("path"));var _=class extends Error{constructor(n){super("\u8BE5\u7B14\u8BB0\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0"),this.name="GenerationJobAlreadyRunningError",this.code="already-running",this.key=n}},x=class extends Error{constructor(n){super("\u751F\u6210\u5DF2\u53D6\u6D88"),this.name="GenerationJobCancelledError",this.code="cancelled",this.key=n}},ve=class{constructor(n){this.key=n,this.phase="queued",this.cancelled=!1,this.startedAt=new Date().toISOString(),this.updatedAt=this.startedAt,this._cancelHandlers=[]}setPhase(n){this.phase=n,this.updatedAt=new Date().toISOString()}onCancel(n){if(typeof n=="function"){if(this.cancelled){n();return}this._cancelHandlers.push(n)}}cancel(){if(this.cancelled)return!1;this.cancelled=!0,this.setPhase("cancelled");for(let n of this._cancelHandlers.splice(0))try{n()}catch{}return!0}throwIfCancelled(){if(this.cancelled)throw new x(this.key)}},H=class{constructor(){this.jobs=new Map}get(n){return this.jobs.get(n)||null}isRunning(n){return this.jobs.has(n)}async start(n,e){if(this.jobs.has(n))throw new _(n);let r=new ve(n);this.jobs.set(n,r);try{r.setPhase("running");let a=await e(r);return r.throwIfCancelled(),a}catch(a){throw r.cancelled&&!(a instanceof x)?new x(n):a}finally{this.jobs.delete(n)}}cancel(n){let e=this.jobs.get(n);return e?e.cancel():!1}};function Ae(t){if(t instanceof x)return"cancelled";let n=t;if(n?.code==="cancelled")return"cancelled";let e=String(n?.message||t);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 ae={zh:{appTitle:"\u5BF9\u7167\u9605\u8BFB\u7B14\u8BB0",settingsTitle:"Parallel Reader \u8BBE\u7F6E",emptyOpenNote:"\u6253\u5F00\u4E00\u7BC7\u7B14\u8BB0\uFF0C\u7136\u540E\u8FD0\u884C\u547D\u4EE4\uFF1A",emptyNoCache:"\u8BE5\u7B14\u8BB0\u5C1A\u65E0\u5BF9\u7167\u7B14\u8BB0\u7F13\u5B58\u3002\u8FD0\u884C\u547D\u4EE4\uFF1A",commandGenerate:"Parallel Reader: \u4E3A\u5F53\u524D\u7B14\u8BB0\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",displayName:"\u5BF9\u7167\u9605\u8BFB\u7B14\u8BB0",ribbonOpen:"\u6253\u5F00\u5BF9\u7167\u7B14\u8BB0\u9762\u677F",cmdRun:"\u4E3A\u5F53\u524D\u7B14\u8BB0\u751F\u6210\u5BF9\u7167\u7B14\u8BB0\uFF08\u7F13\u5B58\u4F18\u5148\uFF09",cmdRegen:"\u5F3A\u5236\u91CD\u65B0\u751F\u6210\uFF08\u7ED5\u8FC7\u7F13\u5B58\uFF09",cmdOpenView:"\u6253\u5F00\u5BF9\u7167\u7B14\u8BB0\u9762\u677F",cmdExport:"\u5BFC\u51FA\u5F53\u524D\u5BF9\u7167\u7B14\u8BB0\u5230 Vault",cmdCopyMarkdown:"\u590D\u5236\u5F53\u524D\u5BF9\u7167\u7B14\u8BB0 Markdown",cmdCancel:"\u53D6\u6D88\u5F53\u524D\u5BF9\u7167\u7B14\u8BB0\u751F\u6210",cmdClearCurrent:"\u6E05\u9664\u5F53\u524D\u7B14\u8BB0\u7684\u7F13\u5B58",cmdClearAll:"\u6E05\u9664\u6240\u6709\u7F13\u5B58",cmdCardPrev:"\u805A\u7126\u4E0A\u4E00\u5F20\u6458\u8981\u5361\u7247",cmdCardNext:"\u805A\u7126\u4E0B\u4E00\u5F20\u6458\u8981\u5361\u7247",cmdCardJump:"\u8DF3\u8F6C\u5230\u5F53\u524D\u6458\u8981\u5361\u7247\u539F\u6587",actionCancel:"\u53D6\u6D88\u751F\u6210",actionRegenerate:"\u91CD\u65B0\u751F\u6210",actionCopyAll:"\u590D\u5236\u5168\u90E8 Markdown",actionExport:"\u5BFC\u51FA\u5230 Vault",staleBanner:"\u6E90\u7B14\u8BB0\u6216\u751F\u6210\u914D\u7F6E\u5DF2\u4FEE\u6539\uFF0C\u5F53\u524D\u662F\u65E7\u7F13\u5B58\u3002",loadingDefault:"\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0...",loadingGenerating:"\u5BF9\u7167\u9605\u8BFB\uFF1A\u8BA9 LLM \u8BFB\u5168\u6587\u5E76\u81EA\u9002\u5E94\u5207\u6BB5...",loadingSubtitle:"\u53EF\u4EE5\u7EE7\u7EED\u9605\u8BFB\u539F\u6587\uFF0C\u751F\u6210\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u5237\u65B0\u53F3\u4FA7\u5361\u7247\u3002",errorTitle:"\u751F\u6210\u5931\u8D25",emptyCard:"\uFF08\u672A\u751F\u6210\uFF09",anchorMismatch:"anchor \u5339\u914D\u5931\u8D25\uFF0C\u65E0\u6CD5\u6EDA\u52A8\u8054\u52A8",menuCopyMarkdown:"\u590D\u5236 Markdown",menuCopyPlain:"\u590D\u5236\u7EAF\u6587\u672C",menuCopyAnchor:"\u590D\u5236 anchor \u5F15\u7528",menuJumpSource:"\u8DF3\u8F6C\u5230\u539F\u6587",menuEditCard:"\u7F16\u8F91\u6B64\u5361\u7247",menuDeleteCard:"\u5220\u9664\u6B64\u5361\u7247",copiedMarkdown:"\u5DF2\u590D\u5236 Markdown",copiedPlain:"\u5DF2\u590D\u5236\u7EAF\u6587\u672C",copiedAnchor:"\u5DF2\u590D\u5236\u5F15\u7528\u539F\u6587",copiedAllMarkdown:"\u5DF2\u590D\u5236\u5168\u90E8 Markdown",copyFailed:"\u590D\u5236\u5931\u8D25\uFF1A{error}",actionFailed:"{label}\u5931\u8D25\uFF1A{error}",exported:"\u5DF2\u5BFC\u51FA \u2192 {path}",noCurrentNote:"\u6CA1\u6709\u5F53\u524D\u7B14\u8BB0",cacheClearedFile:"\u5DF2\u6E05\u9664\u7F13\u5B58\uFF1A{name}",cacheClearedAll:"\u5DF2\u6E05\u9664 {count} \u6761\u7F13\u5B58",noCancelableJob:"\u5F53\u524D\u6CA1\u6709\u53EF\u53D6\u6D88\u7684\u751F\u6210\u4EFB\u52A1",cancelRequested:"\u5DF2\u8BF7\u6C42\u53D6\u6D88\u751F\u6210",cancelRequestedApiInFlight:"\u5DF2\u8BF7\u6C42\u53D6\u6D88\u751F\u6210\uFF1B\u5F53\u524D API \u8BF7\u6C42\u65E0\u6CD5\u7ACB\u5373\u4E2D\u65AD\uFF0C\u8FD4\u56DE\u540E\u4F1A\u4E22\u5F03\u7ED3\u679C\u3002",fileMenuGenerate:"\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",fileMenuRegen:"\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",fileMenuClear:"\u6E05\u9664\u5BF9\u7167\u7B14\u8BB0\u7F13\u5B58",noExportContent:"\u5F53\u524D\u6CA1\u6709\u53EF\u5BFC\u51FA\u7684\u5BF9\u7167\u7B14\u8BB0",noCopyContent:"\u5F53\u524D\u6CA1\u6709\u53EF\u590D\u5236\u7684\u5BF9\u7167\u7B14\u8BB0",noActiveCard:"\u5F53\u524D\u6CA1\u6709\u53EF\u8DF3\u8F6C\u7684\u6458\u8981\u5361\u7247",confirmRegenerateEditedCards:"\u8FD9\u7BC7\u7B14\u8BB0\u7684\u5BF9\u7167\u5361\u7247\u5DF2\u88AB\u624B\u52A8\u7F16\u8F91\u3002\u91CD\u65B0\u751F\u6210\u4F1A\u8986\u76D6\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",regenerateCancelled:"\u5DF2\u53D6\u6D88\u91CD\u65B0\u751F\u6210",cardDeleted:"\u5DF2\u5220\u9664\u6B64\u5361\u7247",cardSaved:"\u5DF2\u4FDD\u5B58\u6B64\u5361\u7247",editCardTitle:"\u7F16\u8F91\u6458\u8981\u5361\u7247",editCardTitleField:"\u6807\u9898",editCardGistField:"\u9886\u8BFB",editCardBulletsField:"\u8981\u70B9\uFF08\u6BCF\u884C\u4E00\u6761\uFF09",editCardCancel:"\u53D6\u6D88",editCardSave:"\u4FDD\u5B58",openNoteFirst:"\u5148\u6253\u5F00\u4E00\u7BC7\u7B14\u8BB0",alreadyGenerating:"\u8BE5\u7B14\u8BB0\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",emptyNote:"\u7B14\u8BB0\u4E3A\u7A7A",longNoteTruncated:"\u7B14\u8BB0\u8F83\u957F\uFF1A\u4EC5\u53D1\u9001\u524D {count} \u4E2A\u5B57\u7B26\u7ED9\u6A21\u578B",generatingNotice:"\u5BF9\u7167\u9605\u8BFB\uFF1A\u8BA9 LLM \u8BFB\u5168\u6587\u5E76\u81EA\u9002\u5E94\u5207\u6BB5\u2026",noCardsReturned:"LLM \u672A\u8FD4\u56DE\u4EFB\u4F55 card",generationDone:"\u5BF9\u7167\u7B14\u8BB0\u751F\u6210\u5B8C\u6210\uFF1A{count} \u6BB5{suffix}",unanchoredSuffix:"\uFF08\u26A0 {count} \u6BB5 anchor \u672A\u5339\u914D\uFF09",cancelled:"\u5DF2\u53D6\u6D88\u751F\u6210",cancelledError:"\u751F\u6210\u5DF2\u53D6\u6D88",generationFailed:"\u751F\u6210\u5931\u8D25{kind}\uFF1A{error}",errorCustomProviderBaseUrlRequired:"\u81EA\u5B9A\u4E49 provider \u9700\u8981\u586B\u5199 API Base URL\u3002",errorApiBaseUrlMissing:"API Base URL \u672A\u8BBE\u7F6E\u3002\u8BF7\u5728\u8BBE\u7F6E\u91CC\u9009\u62E9 provider \u6216\u586B\u5199\u81EA\u5B9A\u4E49 base URL\u3002",errorModelMissing:"Model \u672A\u8BBE\u7F6E\u3002\u8BF7\u5728\u8BBE\u7F6E\u91CC\u586B\u5199\u6A21\u578B ID\u3002",errorApiKeyMissing:"API key \u672A\u8BBE\u7F6E\u3002\u8BF7\u5728\u8BBE\u7F6E\u91CC\u586B\u5199 API Key{hint}\u3002",errorApiKeyEnvHint:" \u6216\u73AF\u5883\u53D8\u91CF {envVar}",errorLlmNonJson:`LLM \u8FD4\u56DE\u975E JSON\uFF1A +"use strict";var ot=Object.create;var W=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var ct=Object.getOwnPropertyNames;var ut=Object.getPrototypeOf,pt=Object.prototype.hasOwnProperty;var dt=(t,n)=>{for(var e in n)W(t,e,{get:n[e],enumerable:!0})},je=(t,n,e,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let a of ct(n))!pt.call(t,a)&&a!==e&&W(t,a,{get:()=>n[a],enumerable:!(r=lt(n,a))||r.enumerable});return t};var Y=(t,n,e)=>(e=t!=null?ot(ut(t)):{},je(n||!t||!t.__esModule?W(e,"default",{value:t,enumerable:!0}):e,t)),ht=t=>je(W({},"__esModule",{value:!0}),t);var Ht={};dt(Ht,{__test:()=>Gt,default:()=>jt});module.exports=ht(Ht);var d=require("obsidian");function X(t,n){if(!n)return-1;let e=u=>u.replace(/\s+/g," ").trim(),r=u=>{let h=[],C=[],m=!1;for(let f=0;f0;continue}m&&(h.push(" "),C.push(f),m=!1),h.push(Ue),C.push(f)}return{text:h.join(""),map:C}},a=u=>{if(!u)return-1;let h=t.indexOf(u);if(h===-1)return-1;let C=0;for(let m=0;m=0||(i=a(n.trim()),i>=0))return i;for(let u of[60,40,25,15]){let h=n.trim().slice(0,u);if(i=a(h),i>=0)return i}let s=r(t),o=e(n).slice(0,30);if(!o)return-1;let l=s.text.indexOf(o);if(l===-1)return-1;let p=s.map[l];if(p==null)return-1;let c=0;for(let u=0;u0}function Z(t,n){let e=Array.isArray(t)?t.slice():[];return!Number.isInteger(n)||n<0||n>=e.length||e.splice(n,1),e}function Q(t,n,e){if(!Number.isInteger(t)||!Number.isInteger(n)||n<=0||!Number.isInteger(e)||e<0||t<0||t>=n)return e;let r=n-1;return r<=0?-1:t=r.length||(r[n]=Object.assign({},r[n],e||{})),r}var ft=require("child_process"),Ke=Y(require("fs")),$e=Y(require("os")),F=Y(require("path"));var L=class extends Error{constructor(n){super("\u8BE5\u7B14\u8BB0\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0"),this.name="GenerationJobAlreadyRunningError",this.code="already-running",this.key=n}},P=class extends Error{constructor(n){super("\u751F\u6210\u5DF2\u53D6\u6D88"),this.name="GenerationJobCancelledError",this.code="cancelled",this.key=n}},ye=class{constructor(n){this.key=n,this.phase="queued",this.cancelled=!1,this.startedAt=new Date().toISOString(),this.updatedAt=this.startedAt,this._cancelHandlers=[]}setPhase(n){this.phase=n,this.updatedAt=new Date().toISOString()}onCancel(n){if(typeof n=="function"){if(this.cancelled){n();return}this._cancelHandlers.push(n)}}cancel(){if(this.cancelled)return!1;this.cancelled=!0,this.setPhase("cancelled");for(let n of this._cancelHandlers.splice(0))try{n()}catch{}return!0}throwIfCancelled(){if(this.cancelled)throw new P(this.key)}},U=class{constructor(){this.jobs=new Map}get(n){return this.jobs.get(n)||null}isRunning(n){return this.jobs.has(n)}async start(n,e){if(this.jobs.has(n))throw new L(n);let r=new ye(n);this.jobs.set(n,r);try{r.setPhase("running");let a=await e(r);return r.throwIfCancelled(),a}catch(a){throw r.cancelled&&!(a instanceof P)?new P(n):a}finally{this.jobs.delete(n)}}cancel(n){let e=this.jobs.get(n);return e?e.cancel():!1}};function Ce(t){if(t instanceof P)return"cancelled";let n=t;if(n?.code==="cancelled")return"cancelled";let e=String(n?.message||t);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={zh:{appTitle:"\u5BF9\u7167\u9605\u8BFB\u7B14\u8BB0",settingsTitle:"Parallel Reader \u8BBE\u7F6E",emptyOpenNote:"\u6253\u5F00\u4E00\u7BC7\u7B14\u8BB0\uFF0C\u7136\u540E\u8FD0\u884C\u547D\u4EE4\uFF1A",emptyNoCache:"\u8BE5\u7B14\u8BB0\u5C1A\u65E0\u5BF9\u7167\u7B14\u8BB0\u7F13\u5B58\u3002\u8FD0\u884C\u547D\u4EE4\uFF1A",commandGenerate:"Parallel Reader: \u4E3A\u5F53\u524D\u7B14\u8BB0\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",displayName:"\u5BF9\u7167\u9605\u8BFB\u7B14\u8BB0",ribbonOpen:"\u6253\u5F00\u5BF9\u7167\u7B14\u8BB0\u9762\u677F",cmdRun:"\u4E3A\u5F53\u524D\u7B14\u8BB0\u751F\u6210\u5BF9\u7167\u7B14\u8BB0\uFF08\u7F13\u5B58\u4F18\u5148\uFF09",cmdRegen:"\u5F3A\u5236\u91CD\u65B0\u751F\u6210\uFF08\u7ED5\u8FC7\u7F13\u5B58\uFF09",cmdOpenView:"\u6253\u5F00\u5BF9\u7167\u7B14\u8BB0\u9762\u677F",cmdExport:"\u5BFC\u51FA\u5F53\u524D\u5BF9\u7167\u7B14\u8BB0\u5230 Vault",cmdCopyMarkdown:"\u590D\u5236\u5F53\u524D\u5BF9\u7167\u7B14\u8BB0 Markdown",cmdCancel:"\u53D6\u6D88\u5F53\u524D\u5BF9\u7167\u7B14\u8BB0\u751F\u6210",cmdClearCurrent:"\u6E05\u9664\u5F53\u524D\u7B14\u8BB0\u7684\u7F13\u5B58",cmdClearAll:"\u6E05\u9664\u6240\u6709\u7F13\u5B58",cmdCardPrev:"\u805A\u7126\u4E0A\u4E00\u5F20\u6458\u8981\u5361\u7247",cmdCardNext:"\u805A\u7126\u4E0B\u4E00\u5F20\u6458\u8981\u5361\u7247",cmdCardJump:"\u8DF3\u8F6C\u5230\u5F53\u524D\u6458\u8981\u5361\u7247\u539F\u6587",actionCancel:"\u53D6\u6D88\u751F\u6210",actionRegenerate:"\u91CD\u65B0\u751F\u6210",actionCopyAll:"\u590D\u5236\u5168\u90E8 Markdown",actionExport:"\u5BFC\u51FA\u5230 Vault",staleBanner:"\u6E90\u7B14\u8BB0\u6216\u751F\u6210\u914D\u7F6E\u5DF2\u4FEE\u6539\uFF0C\u5F53\u524D\u662F\u65E7\u7F13\u5B58\u3002",loadingDefault:"\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0...",loadingGenerating:"\u5BF9\u7167\u9605\u8BFB\uFF1A\u8BA9 LLM \u8BFB\u5168\u6587\u5E76\u81EA\u9002\u5E94\u5207\u6BB5...",loadingSubtitle:"\u53EF\u4EE5\u7EE7\u7EED\u9605\u8BFB\u539F\u6587\uFF0C\u751F\u6210\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u5237\u65B0\u53F3\u4FA7\u5361\u7247\u3002",errorTitle:"\u751F\u6210\u5931\u8D25",emptyCard:"\uFF08\u672A\u751F\u6210\uFF09",anchorMismatch:"anchor \u5339\u914D\u5931\u8D25\uFF0C\u65E0\u6CD5\u6EDA\u52A8\u8054\u52A8",menuCopyMarkdown:"\u590D\u5236 Markdown",menuCopyPlain:"\u590D\u5236\u7EAF\u6587\u672C",menuCopyAnchor:"\u590D\u5236 anchor \u5F15\u7528",menuJumpSource:"\u8DF3\u8F6C\u5230\u539F\u6587",menuEditCard:"\u7F16\u8F91\u6B64\u5361\u7247",menuDeleteCard:"\u5220\u9664\u6B64\u5361\u7247",copiedMarkdown:"\u5DF2\u590D\u5236 Markdown",copiedPlain:"\u5DF2\u590D\u5236\u7EAF\u6587\u672C",copiedAnchor:"\u5DF2\u590D\u5236\u5F15\u7528\u539F\u6587",copiedAllMarkdown:"\u5DF2\u590D\u5236\u5168\u90E8 Markdown",copyFailed:"\u590D\u5236\u5931\u8D25\uFF1A{error}",actionFailed:"{label}\u5931\u8D25\uFF1A{error}",exported:"\u5DF2\u5BFC\u51FA \u2192 {path}",noCurrentNote:"\u6CA1\u6709\u5F53\u524D\u7B14\u8BB0",cacheClearedFile:"\u5DF2\u6E05\u9664\u7F13\u5B58\uFF1A{name}",cacheClearedAll:"\u5DF2\u6E05\u9664 {count} \u6761\u7F13\u5B58",noCancelableJob:"\u5F53\u524D\u6CA1\u6709\u53EF\u53D6\u6D88\u7684\u751F\u6210\u4EFB\u52A1",cancelRequested:"\u5DF2\u8BF7\u6C42\u53D6\u6D88\u751F\u6210",cancelRequestedApiInFlight:"\u5DF2\u8BF7\u6C42\u53D6\u6D88\u751F\u6210\uFF1B\u5F53\u524D API \u8BF7\u6C42\u65E0\u6CD5\u7ACB\u5373\u4E2D\u65AD\uFF0C\u8FD4\u56DE\u540E\u4F1A\u4E22\u5F03\u7ED3\u679C\u3002",fileMenuGenerate:"\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",fileMenuRegen:"\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",fileMenuClear:"\u6E05\u9664\u5BF9\u7167\u7B14\u8BB0\u7F13\u5B58",noExportContent:"\u5F53\u524D\u6CA1\u6709\u53EF\u5BFC\u51FA\u7684\u5BF9\u7167\u7B14\u8BB0",noCopyContent:"\u5F53\u524D\u6CA1\u6709\u53EF\u590D\u5236\u7684\u5BF9\u7167\u7B14\u8BB0",noActiveCard:"\u5F53\u524D\u6CA1\u6709\u53EF\u8DF3\u8F6C\u7684\u6458\u8981\u5361\u7247",confirmRegenerateEditedCards:"\u8FD9\u7BC7\u7B14\u8BB0\u7684\u5BF9\u7167\u5361\u7247\u5DF2\u88AB\u624B\u52A8\u7F16\u8F91\u3002\u91CD\u65B0\u751F\u6210\u4F1A\u8986\u76D6\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",regenerateCancelled:"\u5DF2\u53D6\u6D88\u91CD\u65B0\u751F\u6210",cardDeleted:"\u5DF2\u5220\u9664\u6B64\u5361\u7247",cardSaved:"\u5DF2\u4FDD\u5B58\u6B64\u5361\u7247",editCardTitle:"\u7F16\u8F91\u6458\u8981\u5361\u7247",editCardTitleField:"\u6807\u9898",editCardGistField:"\u9886\u8BFB",editCardBulletsField:"\u8981\u70B9\uFF08\u6BCF\u884C\u4E00\u6761\uFF09",editCardCancel:"\u53D6\u6D88",editCardSave:"\u4FDD\u5B58",openNoteFirst:"\u5148\u6253\u5F00\u4E00\u7BC7\u7B14\u8BB0",alreadyGenerating:"\u8BE5\u7B14\u8BB0\u6B63\u5728\u751F\u6210\u5BF9\u7167\u7B14\u8BB0",emptyNote:"\u7B14\u8BB0\u4E3A\u7A7A",longNoteTruncated:"\u7B14\u8BB0\u8F83\u957F\uFF1A\u4EC5\u53D1\u9001\u524D {count} \u4E2A\u5B57\u7B26\u7ED9\u6A21\u578B",generatingNotice:"\u5BF9\u7167\u9605\u8BFB\uFF1A\u8BA9 LLM \u8BFB\u5168\u6587\u5E76\u81EA\u9002\u5E94\u5207\u6BB5\u2026",noCardsReturned:"LLM \u672A\u8FD4\u56DE\u4EFB\u4F55 card",generationDone:"\u5BF9\u7167\u7B14\u8BB0\u751F\u6210\u5B8C\u6210\uFF1A{count} \u6BB5{suffix}",unanchoredSuffix:"\uFF08\u26A0 {count} \u6BB5 anchor \u672A\u5339\u914D\uFF09",cancelled:"\u5DF2\u53D6\u6D88\u751F\u6210",cancelledError:"\u751F\u6210\u5DF2\u53D6\u6D88",generationFailed:"\u751F\u6210\u5931\u8D25{kind}\uFF1A{error}",errorCustomProviderBaseUrlRequired:"\u81EA\u5B9A\u4E49 provider \u9700\u8981\u586B\u5199 API Base URL\u3002",errorApiBaseUrlMissing:"API Base URL \u672A\u8BBE\u7F6E\u3002\u8BF7\u5728\u8BBE\u7F6E\u91CC\u9009\u62E9 provider \u6216\u586B\u5199\u81EA\u5B9A\u4E49 base URL\u3002",errorModelMissing:"Model \u672A\u8BBE\u7F6E\u3002\u8BF7\u5728\u8BBE\u7F6E\u91CC\u586B\u5199\u6A21\u578B ID\u3002",errorApiKeyMissing:"API key \u672A\u8BBE\u7F6E\u3002\u8BF7\u5728\u8BBE\u7F6E\u91CC\u586B\u5199 API Key{hint}\u3002",errorApiKeyEnvHint:" \u6216\u73AF\u5883\u53D8\u91CF {envVar}",errorLlmNonJson:`LLM \u8FD4\u56DE\u975E JSON\uFF1A {excerpt}`,errorCustomHeadersJsonParse:"\u81EA\u5B9A\u4E49 headers JSON \u89E3\u6790\u5931\u8D25\uFF1A{error}",errorCustomHeadersJsonObject:"\u81EA\u5B9A\u4E49 headers JSON \u5FC5\u987B\u662F\u5BF9\u8C61",errorCustomHeadersLineFormat:"\u81EA\u5B9A\u4E49 headers \u6BCF\u884C\u683C\u5F0F\u5E94\u4E3A `Header-Name: value`",errorProviderNonJson:`{label} \u8FD4\u56DE\u975E JSON\uFF1A {excerpt}`,errorProviderRequestFailed:"{label} \u8BF7\u6C42\u5931\u8D25\uFF1A{error}",errorProviderApiStatus:"{label} API \u8FD4\u56DE HTTP {status}\uFF1A{excerpt}",noEditor:"\u627E\u4E0D\u5230\u6E90\u7B14\u8BB0\u5BF9\u5E94\u7684\u7F16\u8F91\u5668\u7A97\u53E3",settingUiLanguageName:"\u754C\u9762\u8BED\u8A00",settingUiLanguageDesc:"\u63A7\u5236\u63D2\u4EF6\u754C\u9762\u3001\u547D\u4EE4\u548C\u63D0\u793A\u6587\u6848\uFF1BAuto \u8DDF\u968F Obsidian/\u7CFB\u7EDF\u8BED\u8A00",settingBackendName:"Backend",settingBackendDesc:"\u751F\u6210 bullet \u7684\u540E\u7AEF\uFF1ACLI \u590D\u7528\u672C\u673A\u767B\u5F55\uFF1BAPI \u652F\u6301 OpenAI/Anthropic/Gemini \u53CA\u517C\u5BB9\u4EE3\u7406",settingCliPathName:"CLI \u8DEF\u5F84\uFF08\u53EF\u9009\uFF09",settingCliPathDesc:"\u7559\u7A7A\u5219\u81EA\u52A8\u63A2\u6D4B\u5E38\u89C1\u4F4D\u7F6E\uFF1BObsidian GUI \u542F\u52A8\u65F6\u4E0D\u7EE7\u627F shell PATH\uFF0C\u5FC5\u8981\u65F6\u586B\u7EDD\u5BF9\u8DEF\u5F84",settingCliPathPlaceholder:"\u4F8B\uFF1A/Users/you/bin/codex",settingCliTimeoutName:"CLI \u8D85\u65F6 (ms)",apiProviderHeader:"API Provider",settingProviderPresetName:"Provider preset",settingProviderPresetDesc:"\u53C2\u8003 OpenClaw \u7684 provider/model \u601D\u8DEF\uFF1Apreset \u53EA\u8D1F\u8D23\u534F\u8BAE\u3001base URL \u548C\u8BA4\u8BC1\u9ED8\u8BA4\u503C",settingApiFormatName:"API format",settingApiFormatDesc:"\u4E0D\u540C provider \u7684 wire protocol\uFF1BOpenAI-compatible \u4EE3\u7406\u901A\u5E38\u9009 Chat Completions",settingBaseUrlName:"Base URL",settingBaseUrlDesc:"\u586B provider \u6839\u5730\u5740\uFF0C\u4E0D\u8981\u9644\u52A0 /chat/completions\uFF1B\u7559\u7A7A\u65F6\u4F7F\u7528 preset \u9ED8\u8BA4\u503C",settingApiKeyName:"API Key",settingApiKeyDesc:"\u672C\u5730 Ollama/LM Studio \u53EF\u7559\u7A7A\uFF1B\u5176\u4ED6 provider \u901A\u5E38\u9700\u8981 key",settingApiKeyEnvName:"API Key \u73AF\u5883\u53D8\u91CF",settingApiKeyEnvDesc:"\u53EF\u9009\uFF1BObsidian GUI \u4E0D\u4E00\u5B9A\u7EE7\u627F shell \u73AF\u5883\uFF0C\u76F4\u63A5\u586B API Key \u66F4\u7A33\u5B9A",settingAuthTypeName:"\u8BA4\u8BC1\u65B9\u5F0F",settingAuthTypeDesc:"Auto \u4F7F\u7528 provider preset\uFF1B\u81EA\u5B9A\u4E49\u4EE3\u7406\u53EF\u6309\u9700\u8981\u6539\u6210 Bearer\u3001x-api-key \u6216 none",settingHeadersName:"\u989D\u5916 headers",settingHeadersDesc:"\u53EF\u9009\u3002\u652F\u6301 JSON \u5BF9\u8C61\u6216\u6BCF\u884C `Header: value`\uFF0C\u7528\u4E8E Cloudflare AI Gateway \u7B49\u4EE3\u7406",settingMaxTokensName:"\u6700\u5927\u8F93\u51FA tokens",settingModelName:"Model",settingModelDescApi:"API \u8C03\u7528\u7684\u6A21\u578B ID\uFF1B\u652F\u6301 OpenClaw \u98CE\u683C provider/model\uFF0C\u82E5 provider \u524D\u7F00\u5339\u914D\u5F53\u524D preset \u4F1A\u81EA\u52A8\u5265\u79BB",settingModelDescCli:"Claude Code \u4E0B\u4F1A\u4F20 --model\uFF1BCodex \u4E0B\u901A\u5E38\u5FFD\u7565\uFF08\u7528 Codex \u9ED8\u8BA4\u914D\u7F6E\uFF09",settingMaxInputName:"\u6700\u5927\u8F93\u5165\u5B57\u7B26\u6570",settingMaxInputDesc:"\u8D85\u8FC7\u8BE5\u957F\u5EA6\u4F1A\u622A\u65AD\u540E\u518D\u53D1\u9001\u7ED9\u6A21\u578B\uFF1B\u957F\u4E0A\u4E0B\u6587\u6A21\u578B\u53EF\u9002\u5F53\u8C03\u5927",promptHeader:"Prompt",settingPromptLanguageName:"\u8F93\u51FA\u8BED\u8A00",settingPromptLanguageDesc:"\u63A7\u5236 title/gist/bullets \u7684\u8BED\u8A00\uFF1Banchor \u59CB\u7EC8\u9010\u5B57\u590D\u5236\u539F\u6587",settingCardRangeName:"\u5361\u7247\u6570\u91CF\u8303\u56F4",settingCardRangeDesc:"\u6A21\u578B\u4F1A\u5728\u8FD9\u4E2A\u8303\u56F4\u5185\u81EA\u9002\u5E94\u5207\u6BB5",settingCustomPromptName:"\u81EA\u5B9A\u4E49 system prompt",settingCustomPromptDesc:"\u53EF\u9009\u3002\u652F\u6301\u53D8\u91CF\uFF1A{minCards}\u3001{maxCards}\u3001{languageInstruction}\u3001{schema}\u3001{example}",settingCustomPromptPlaceholder:"\u7559\u7A7A\u4F7F\u7528\u5185\u7F6E prompt",settingTestBackendName:"\u6D4B\u8BD5\u5F53\u524D\u540E\u7AEF",settingTestBackendDescApi:"\u4F1A\u53D1\u8D77\u4E00\u6B21\u6700\u5C0F LLM \u8BF7\u6C42\u9A8C\u8BC1 API \u8BBE\u7F6E",settingTestBackendDescCli:"\u8C03\u7528 ` --version` \u9A8C\u8BC1 spawn \u80FD\u627E\u5230\u4E8C\u8FDB\u5236",backendTestFailed:"\u2717 \u540E\u7AEF\u6D4B\u8BD5\u5931\u8D25\uFF1A{error}",settingExportFolderName:"\u5BFC\u51FA\u6587\u4EF6\u5939",settingExportFolderDesc:"\u5BF9\u7167\u7B14\u8BB0\u751F\u6210\u4F4D\u7F6E\uFF08\u76F8\u5BF9 Vault \u6839\uFF09",cacheHeader:"\u7F13\u5B58",settingMaxCacheName:"\u6700\u5927\u7F13\u5B58\u7BC7\u6570",settingMaxCacheDesc:"\u8D85\u8FC7\u4E0A\u9650\u540E\u6309\u6700\u8FD1\u8BBF\u95EE\u65F6\u95F4\u6DD8\u6C70\u6700\u65E7\u7684\u7B14\u8BB0\u7F13\u5B58\uFF1B\u7F13\u5B58\u4FDD\u5B58\u5728\u63D2\u4EF6\u76EE\u5F55\u7684 cache.json",cachePruned:"\u5DF2\u6DD8\u6C70 {count} \u6761\u65E7\u7F13\u5B58",cachedNotesName:"\u5DF2\u7F13\u5B58\u7B14\u8BB0\uFF1A{count} \u7BC7",cachedNotesDesc:"\u7F13\u5B58\u4EE5\u6E90\u7B14\u8BB0 SHA1 + \u751F\u6210\u914D\u7F6E\u6307\u7EB9\u4F5C\u4E3A\u5931\u6548\u952E\uFF0C\u6E90\u7B14\u8BB0\u6216\u6A21\u578B\u914D\u7F6E\u4FEE\u6539\u540E\u4F1A\u663E\u793A stale \u63D0\u793A",clearAllCacheButton:"\u6E05\u9664\u6240\u6709\u7F13\u5B58",settingStreamingName:"\u6D41\u5F0F\u8F93\u51FA",settingStreamingDesc:"\u542F\u7528\u540E\u751F\u6210\u65F6\u5B9E\u65F6\u663E\u793A LLM \u8F93\u51FA\u8FDB\u5EA6\uFF08\u4EC5 OpenAI Chat \u548C Anthropic \u683C\u5F0F\u652F\u6301\uFF09"},en:{appTitle:"Parallel Reader",settingsTitle:"Parallel Reader Settings",emptyOpenNote:"Open a note, then run:",emptyNoCache:"This note has no cached parallel notes. Run:",commandGenerate:"Parallel Reader: Generate notes for current note",displayName:"Parallel Reader",ribbonOpen:"Open Parallel Reader pane",cmdRun:"Generate parallel notes for current note (cache first)",cmdRegen:"Regenerate parallel notes (bypass cache)",cmdOpenView:"Open Parallel Reader pane",cmdExport:"Export current parallel notes to Vault",cmdCopyMarkdown:"Copy current parallel notes as Markdown",cmdCancel:"Cancel current parallel-note generation",cmdClearCurrent:"Clear cache for current note",cmdClearAll:"Clear all caches",cmdCardPrev:"Focus previous summary card",cmdCardNext:"Focus next summary card",cmdCardJump:"Jump current summary card to source",actionCancel:"Cancel generation",actionRegenerate:"Regenerate",actionCopyAll:"Copy all Markdown",actionExport:"Export to Vault",staleBanner:"The source note or generation settings changed. This is stale cache.",loadingDefault:"Generating parallel notes...",loadingGenerating:"Parallel Reader: asking the LLM to read and segment the full note...",loadingSubtitle:"You can keep reading. Cards will refresh when generation finishes.",errorTitle:"Generation failed",emptyCard:"(not generated)",anchorMismatch:"Anchor did not match; scroll sync is unavailable",menuCopyMarkdown:"Copy Markdown",menuCopyPlain:"Copy plain text",menuCopyAnchor:"Copy anchor quote",menuJumpSource:"Jump to source",menuEditCard:"Edit this card",menuDeleteCard:"Delete this card",copiedMarkdown:"Copied Markdown",copiedPlain:"Copied plain text",copiedAnchor:"Copied anchor quote",copiedAllMarkdown:"Copied all Markdown",copyFailed:"Copy failed: {error}",actionFailed:"{label} failed: {error}",exported:"Exported \u2192 {path}",noCurrentNote:"No current note",cacheClearedFile:"Cleared cache: {name}",cacheClearedAll:"Cleared {count} cache entries",noCancelableJob:"No cancellable generation job",cancelRequested:"Cancel requested",cancelRequestedApiInFlight:"Cancel requested. The in-flight API request cannot be aborted immediately; its result will be ignored.",fileMenuGenerate:"Generate parallel notes",fileMenuRegen:"Regenerate parallel notes",fileMenuClear:"Clear parallel-note cache",noExportContent:"No parallel notes to export",noCopyContent:"No parallel notes to copy",noActiveCard:"No active summary card to jump",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)"}};function Ct(t){let n=t?.uiLanguage;return n==="zh"||n==="en"?n:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function b(t,n,e){let r=Ct(t),a=ae[r]||ae.en,i=ae.en[n]||ae.zh[n]||n,s=a[n]||i;return String(s).replace(/\{([a-zA-Z0-9_]+)\}/g,(o,l)=>e&&Object.hasOwn(e,l)?String(e[l]):o)}var ie="record_parallel_reader_cards";function bt(t){let n=[],e=-1,r=0,a=!1,i=!1;for(let s=0;s0&&r--,r===0&&e>=0&&(n.push(t.slice(e,s+1)),e=-1))}return n}function xe(t){let n=(t||"").trim();if(!n)return n;try{return JSON.parse(n),n}catch{}let e=n.match(/```(?:json)?\s*([\s\S]*?)```/);if(e){let a=e[1].trim();try{return JSON.parse(a),a}catch{}}let r=bt(n);r.sort((a,i)=>i.length-a.length);for(let a of r)try{return JSON.parse(a),a}catch{}return n}function P(t,n){let e=xe(t),r;try{r=JSON.parse(e)}catch{throw new Error(b(n||null,"errorLlmNonJson",{excerpt:(t||"").slice(0,500)}))}return j(r)}function j(t){return(t&&Array.isArray(t.cards)?t.cards:[]).filter(e=>!!e&&typeof e=="object").map(e=>({title:typeof e.title=="string"?e.title:"(\u65E0\u6807\u9898)",anchor:typeof e.anchor=="string"?e.anchor:"",gist:typeof e.gist=="string"?e.gist:"",bullets:Array.isArray(e.bullets)?e.bullets.filter(r=>typeof r=="string"):[]}))}function J(t){let n={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:n}},required:["cards"]};return t&&(n.additionalProperties=!1,e.additionalProperties=!1),e}function $e(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:J(!0)}}}function ze(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:J(!0)}}}function qe(){return{name:ie,description:"Return the generated Parallel Reader summary cards.",input_schema:J(!0)}}function M(t,n){if(n?.trim())return n.trim();let e=se.default.homedir(),r=[T.default.join(e,"bin",t),T.default.join(e,".local/bin",t),T.default.join(e,".claude/local",t),T.default.join(e,".codex/bin",t),T.default.join(e,".bun/bin",t),T.default.join(e,".npm-global/bin",t),T.default.join(e,".cargo/bin",t),"/opt/homebrew/bin/"+t,"/usr/local/bin/"+t];for(let a of r)try{if(Ye.default.existsSync(a))return a}catch{}return t}function K(t,n,e,r,a){return new Promise((i,s)=>{let o,l=!1,d=null,c=u=>{l||(l=!0,d&&clearTimeout(d),s(u))},p=u=>{l||(l=!0,d&&clearTimeout(d),i(u))};try{o=(0,We.spawn)(t,n,{stdio:["pipe","pipe","pipe"],env:{...process.env,PATH:[process.env.PATH||"","/usr/local/bin","/opt/homebrew/bin",T.default.join(se.default.homedir(),".local/bin"),T.default.join(se.default.homedir(),".claude/local")].filter(Boolean).join(":")}})}catch(u){return s(new Error(`Failed to start ${t}: ${u.message}`))}let h="",f="";if(d=setTimeout(()=>{try{o.kill("SIGKILL")}catch{}c(new Error(`CLI timed out (${r}ms)`))},r),a&&a.onCancel(()=>{try{o.kill("SIGKILL")}catch{}c(new x(a.key))}),o.stdout.on("data",u=>{h+=u.toString("utf8")}),o.stderr.on("data",u=>{f+=u.toString("utf8")}),o.on("error",u=>{c(new Error(`CLI startup error: ${u.message}. Try setting an absolute CLI path.`))}),o.on("close",u=>{if(!l){if(u!==0)return c(new Error(`CLI exited with code ${u} -stderr: -${f.slice(0,1e3)}`));p({stdout:h,stderr:f})}}),e)try{o.stdin.write(e),o.stdin.end()}catch{}else try{o.stdin.end()}catch{}})}async function Xe(t,n,e,r){let a=M("claude",e.cliPath),i=["-p","--output-format","json","--append-system-prompt",t,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"];e.model&&i.push("--model",e.model);let{stdout:s}=await K(a,i,n,e.cliTimeoutMs,r),o;try{o=JSON.parse(s)}catch{throw new Error(`claude CLI returned a non-JSON envelope: -`+s.slice(0,500))}let l=o.result||o.content||"";return P(l,e)}async function Ze(t,n,e,r){let a=M("codex",e.cliPath),i=`<> -${t} -<> -${n} - -Output JSON directly with no explanation.`,s=["exec","--skip-git-repo-check","-"],{stdout:o}=await K(a,s,i,e.cliTimeoutMs,r);return P(o,e)}function Pe(t){let n=[`## ${t.title}`];if(t.anchor){let e=t.anchor.replace(/\s+/g," ").trim();n.push(`> ${e}`)}return t.gist&&n.push(t.gist),t.bullets&&t.bullets.length>0&&n.push(t.bullets.map(e=>`- ${e}`).join(` +{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)"}};function mt(t){let n=t?.uiLanguage;return n==="zh"||n==="en"?n:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function b(t,n,e){let r=mt(t),a=te[r]||te.en,i=te.en[n]||te.zh[n]||n,s=a[n]||i;return String(s).replace(/\{([a-zA-Z0-9_]+)\}/g,(o,l)=>e&&Object.hasOwn(e,l)?String(e[l]):o)}var re="record_parallel_reader_cards";function gt(t){let n=[],e=-1,r=0,a=!1,i=!1;for(let s=0;s0&&r--,r===0&&e>=0&&(n.push(t.slice(e,s+1)),e=-1))}return n}function be(t){let n=(t||"").trim();if(!n)return n;try{return JSON.parse(n),n}catch{}let e=n.match(/```(?:json)?\s*([\s\S]*?)```/);if(e){let a=e[1].trim();try{return JSON.parse(a),a}catch{}}let r=gt(n);r.sort((a,i)=>i.length-a.length);for(let a of r)try{return JSON.parse(a),a}catch{}return n}function T(t,n){let e=be(t),r;try{r=JSON.parse(e)}catch{throw new Error(b(n||null,"errorLlmNonJson",{excerpt:(t||"").slice(0,500)}))}return j(r)}function j(t){return(t&&Array.isArray(t.cards)?t.cards:[]).filter(e=>!!e&&typeof e=="object").map(e=>({title:typeof e.title=="string"?e.title:"(\u65E0\u6807\u9898)",anchor:typeof e.anchor=="string"?e.anchor:"",gist:typeof e.gist=="string"?e.gist:"",bullets:Array.isArray(e.bullets)?e.bullets.filter(r=>typeof r=="string"):[]}))}function G(t){let n={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:n}},required:["cards"]};return t&&(n.additionalProperties=!1,e.additionalProperties=!1),e}function Ge(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:G(!0)}}}function He(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:G(!0)}}}function Je(){return{name:re,description:"Return the generated Parallel Reader summary cards.",input_schema:G(!0)}}function ze(t,n){if(n?.trim())return n.trim();let e=$e.default.homedir(),r=[F.default.join(e,"bin",t),F.default.join(e,".local/bin",t),F.default.join(e,".claude/local",t),F.default.join(e,".codex/bin",t),F.default.join(e,".bun/bin",t),F.default.join(e,".npm-global/bin",t),F.default.join(e,".cargo/bin",t),"/opt/homebrew/bin/"+t,"/usr/local/bin/"+t];for(let a of r)try{if(Ke.default.existsSync(a))return a}catch{}return t}function we(t){let n=[`## ${t.title}`];if(t.anchor){let e=t.anchor.replace(/\s+/g," ").trim();n.push(`> ${e}`)}return t.gist&&n.push(t.gist),t.bullets&&t.bullets.length>0&&n.push(t.bullets.map(e=>`- ${e}`).join(` `)),n.join(` -`)}function Qe(t){return[t.title,t.gist||"",...(t.bullets||[]).map(n=>"\u2022 "+n)].filter(Boolean).join(` -`)}function $(t,n){let e=[`# ${t||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let r of n||[])e.push(Pe(r));return e.join(` +`)}function qe(t){return[t.title,t.gist||"",...(t.bullets||[]).map(n=>"\u2022 "+n)].filter(Boolean).join(` +`)}function H(t,n){let e=[`# ${t||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let r of n||[])e.push(we(r));return e.join(` -`)}function oe(t,n,e){if(!Number.isFinite(n)||n<=0)return-1;let r=Math.floor(n),a=e<0?-1:1;return t<0||t>=r?a<0?r-1:0:Math.min(r-1,Math.max(0,t+a))}function le(t,n){if(!Array.isArray(t)||n<0||n>=t.length)return-1;let e=Number(t[n]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}var rt=Q(require("crypto"));var Te=2e4,wt=2,z=2,B=100,q={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},Ee={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},y={uiLanguage:"auto",backend:"claude-code",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Te,maxCacheEntries:B,promptLanguage:"zh",minCards:5,maxCards:15,customSystemPrompt:"",model:"claude-sonnet-4-6",exportFolder:"Reading/Articles",cliTimeoutMs:12e4,streaming:!0},S={"anthropic-messages":{label:"Anthropic Messages",defaultBaseUrl:"https://api.anthropic.com/v1",defaultAuthType:"x-api-key"},"openai-chat":{label:"OpenAI Chat Completions",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer",tokenLimitField:"max_tokens"},"openai-responses":{label:"OpenAI Responses",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer"},"google-generative-ai":{label:"Google Gemini generateContent",defaultBaseUrl:"https://generativelanguage.googleapis.com/v1beta",defaultAuthType:"x-goog-api-key"}},ke={auto:"Auto",bearer:"Authorization: Bearer","x-api-key":"x-api-key","x-goog-api-key":"x-goog-api-key","api-key":"api-key",none:"None"},F={anthropic:{label:"Anthropic",format:"anthropic-messages",baseUrl:"https://api.anthropic.com/v1",authType:"x-api-key",envVar:"ANTHROPIC_API_KEY",model:"claude-sonnet-4-6"},openai:{label:"OpenAI",format:"openai-chat",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",tokenLimitField:"max_completion_tokens",model:"gpt-5.1"},"openai-responses":{label:"OpenAI Responses",format:"openai-responses",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",modelPrefix:"openai",model:"gpt-5.1"},google:{label:"Google Gemini",format:"google-generative-ai",baseUrl:"https://generativelanguage.googleapis.com/v1beta",authType:"x-goog-api-key",envVar:"GEMINI_API_KEY",model:"gemini-3-pro-preview"},openrouter:{label:"OpenRouter",format:"openai-chat",baseUrl:"https://openrouter.ai/api/v1",authType:"bearer",envVar:"OPENROUTER_API_KEY",model:""},groq:{label:"Groq",format:"openai-chat",baseUrl:"https://api.groq.com/openai/v1",authType:"bearer",envVar:"GROQ_API_KEY",model:""},deepseek:{label:"DeepSeek",format:"openai-chat",baseUrl:"https://api.deepseek.com",authType:"bearer",envVar:"DEEPSEEK_API_KEY",model:"deepseek-chat"},moonshot:{label:"Moonshot / Kimi",format:"openai-chat",baseUrl:"https://api.moonshot.ai/v1",authType:"bearer",envVar:"MOONSHOT_API_KEY",model:"kimi-k2.5"},qianfan:{label:"Baidu Qianfan",format:"openai-chat",baseUrl:"https://qianfan.baidubce.com/v2",authType:"bearer",envVar:"QIANFAN_API_KEY",model:"deepseek-v3.2"},minimax:{label:"MiniMax (Anthropic-compatible)",format:"anthropic-messages",baseUrl:"https://api.minimax.io/anthropic",authType:"bearer",envVar:"MINIMAX_API_KEY",model:"MiniMax-M2.1"},xai:{label:"xAI",format:"openai-chat",baseUrl:"https://api.x.ai/v1",authType:"bearer",envVar:"XAI_API_KEY",model:""},mistral:{label:"Mistral",format:"openai-chat",baseUrl:"https://api.mistral.ai/v1",authType:"bearer",envVar:"MISTRAL_API_KEY",model:""},cerebras:{label:"Cerebras",format:"openai-chat",baseUrl:"https://api.cerebras.ai/v1",authType:"bearer",envVar:"CEREBRAS_API_KEY",model:""},zai:{label:"Z.AI / GLM",format:"openai-chat",baseUrl:"https://api.z.ai/api/paas/v4",authType:"bearer",envVar:"ZAI_API_KEY",model:""},ollama:{label:"Ollama (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:11434/v1",authType:"none",envVar:"",model:""},lmstudio:{label:"LM Studio (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:1234/v1",authType:"none",envVar:"",model:""},"custom-openai":{label:"Custom OpenAI-compatible",format:"openai-chat",baseUrl:"",authType:"bearer",envVar:"",model:""},"custom-anthropic":{label:"Custom Anthropic-compatible",format:"anthropic-messages",baseUrl:"",authType:"x-api-key",envVar:"",model:""}};function V(t){return rt.default.createHash("sha1").update(t,"utf8").digest("hex")}function Se(t){return Array.isArray(t)?"["+t.map(Se).join(",")+"]":t&&typeof t=="object"?"{"+Object.keys(t).sort().map(n=>JSON.stringify(n)+":"+Se(t[n])).join(",")+"}":JSON.stringify(t)}function I(t){return t==="api"||t==="anthropic-api"}function w(t){return F[t.apiProvider]||F.anthropic}function A(t){let n=w(t),e=(t.apiFormat||n.format||"").trim();return S[e]?e:n.format}function k(t){let n=A(t),e=w(t),r=(t.apiBaseUrl||"").trim();if(r)return r.replace(/\/+$/,"");if((t.apiProvider||"").startsWith("custom-"))throw new Error(b(t,"errorCustomProviderBaseUrlRequired"));let a=(e.baseUrl||S[n].defaultBaseUrl||"").trim();if(!a)throw new Error(b(t,"errorApiBaseUrlMissing"));return a.replace(/\/+$/,"")}function Fe(t){let n=(t.apiAuthType||"auto").trim();if(n&&n!=="auto")return n;let e=w(t),r=A(t);return e.authType||S[r].defaultAuthType||"bearer"}function nt(t){let n=(t.apiKey||"").trim();if(n)return n;let e=(t.apiKeyEnvVar||w(t).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function L(t){let n=(t.model||"").trim();if(!n)throw new Error(b(t,"errorModelMissing"));let e=w(t),r=[t.apiProvider,e.modelPrefix].map(i=>(i||"").trim()).filter(Boolean),a=n.toLowerCase();for(let i of r){let s=i.toLowerCase();if(a.startsWith(s+"/"))return n.slice(i.length+1).trim()}return n}function Ne(t,n){let e=w(t),r=F[n]||F.anthropic,a=(t.model||"").trim(),i=!a||a===y.model||!!e.model&&a===e.model;t.apiProvider=n,t.apiFormat=r.format,t.apiBaseUrl=r.baseUrl,t.apiAuthType=r.authType||"auto",t.apiKeyEnvVar=r.envVar||"",i&&(t.model=r.model||"")}function De(t){Ee[t.uiLanguage]||(t.uiLanguage=y.uiLanguage),(!t.apiProvider||!F[t.apiProvider])&&(t.apiProvider="anthropic");let n=w(t);(!t.apiFormat||!S[t.apiFormat])&&(t.apiFormat=n.format),(!t.apiAuthType||!ke[t.apiAuthType])&&(t.apiAuthType="auto"),t.backend==="anthropic-api"&&(t.apiProvider=t.apiProvider||"anthropic",t.apiFormat=t.apiFormat||"anthropic-messages",t.apiBaseUrl=t.apiBaseUrl||F.anthropic.baseUrl,t.apiAuthType=t.apiAuthType||"x-api-key",t.apiKeyEnvVar=t.apiKeyEnvVar||"ANTHROPIC_API_KEY");let e=Number(t.apiMaxTokens);(!Number.isFinite(e)||e<=0)&&(t.apiMaxTokens=y.apiMaxTokens);let r=Number(t.maxDocChars);return(!Number.isFinite(r)||r<1e3)&&(t.maxDocChars=y.maxDocChars),t.maxCacheEntries=at(t.maxCacheEntries),q[t.promptLanguage]||(t.promptLanguage=y.promptLanguage),t.minCards=et(t.minCards,y.minCards),t.maxCards=et(t.maxCards,y.maxCards),t.maxCardstt(s)-tt(l)||i.localeCompare(o)).slice(0,r.length-e).map(([i])=>i);for(let i of a)delete t[i];return a}function ce(t){let n=De(Object.assign({},y,t||{})),e=I(n.backend),r=w(n),a=A(n),i=e?(n.apiBaseUrl||r.baseUrl||S[a]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return V(Se({cacheSchemaVersion:z,promptVersion:wt,maxDocChars:Number(n.maxDocChars)||y.maxDocChars,promptLanguage:n.promptLanguage,minCards:n.minCards,maxCards:n.maxCards,customSystemPromptHash:V(n.customSystemPrompt||""),backend:n.backend,model:n.model,apiProvider:e?n.apiProvider:"",apiFormat:e?a:"",apiBaseUrl:i,apiAuthType:e?Fe(n):"",apiHeadersHash:e?V(n.apiHeaders||""):"",apiMaxTokens:e?Number(n.apiMaxTokens)||y.apiMaxTokens:0,structuredOutputVersion:1}))}function ue(t,n,e){return!!t&&t.schemaVersion===z&&t.contentHash===V(n)&&t.settingsHash===ce(e)}function vt(t){return t==="en"?"Write title, gist, and bullets in English.":t==="auto"?"Write title, gist, and bullets in the main language of the source document.":"\u7528\u4E2D\u6587\u8F93\u51FA title\u3001gist \u548C bullets\u3002"}function At(t){return t==="en"?`{"cards":[ +`)}function ne(t,n,e){if(!Number.isFinite(n)||n<=0)return-1;let r=Math.floor(n),a=e<0?-1:1;return t<0||t>=r?a<0?r-1:0:Math.min(r-1,Math.max(0,t+a))}function ae(t,n){if(!Array.isArray(t)||n<0||n>=t.length)return-1;let e=Number(t[n]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}var Xe=Y(require("crypto"));var Ae=2e4,yt=2,J=2,_=100,K={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},xe={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},y={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Ae,maxCacheEntries:_,promptLanguage:"zh",minCards:5,maxCards:15,customSystemPrompt:"",model:"claude-sonnet-4-6",exportFolder:"Reading/Articles",cliTimeoutMs:12e4,streaming:!0},x={"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"}},Pe={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"},k={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 O(t){return Xe.default.createHash("sha1").update(t,"utf8").digest("hex")}function ve(t){return Array.isArray(t)?"["+t.map(ve).join(",")+"]":t&&typeof t=="object"?"{"+Object.keys(t).sort().map(n=>JSON.stringify(n)+":"+ve(t[n])).join(",")+"}":JSON.stringify(t)}function Se(t){return t==="api"||t==="anthropic-api"}function w(t){return k[t.apiProvider]||k.anthropic}function A(t){let n=w(t),e=(t.apiFormat||n.format||"").trim();return x[e]?e:n.format}function E(t){let n=A(t),e=w(t),r=(t.apiBaseUrl||"").trim();if(r)return r.replace(/\/+$/,"");if((t.apiProvider||"").startsWith("custom-"))throw new Error(b(t,"errorCustomProviderBaseUrlRequired"));let a=(e.baseUrl||x[n].defaultBaseUrl||"").trim();if(!a)throw new Error(b(t,"errorApiBaseUrlMissing"));return a.replace(/\/+$/,"")}function Te(t){let n=(t.apiAuthType||"auto").trim();if(n&&n!=="auto")return n;let e=w(t),r=A(t);return e.authType||x[r].defaultAuthType||"bearer"}function Ze(t){let n=(t.apiKey||"").trim();if(n)return n;let e=(t.apiKeyEnvVar||w(t).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function M(t){let n=(t.model||"").trim();if(!n)throw new Error(b(t,"errorModelMissing"));let e=w(t),r=[t.apiProvider,e.modelPrefix].map(i=>(i||"").trim()).filter(Boolean),a=n.toLowerCase();for(let i of r){let s=i.toLowerCase();if(a.startsWith(s+"/"))return n.slice(i.length+1).trim()}return n}function Qe(t,n){let e=w(t),r=k[n]||k.anthropic,a=(t.model||"").trim(),i=!a||a===y.model||!!e.model&&a===e.model;t.apiProvider=n,t.apiFormat=r.format,t.apiBaseUrl=r.baseUrl,t.apiAuthType=r.authType||"auto",t.apiKeyEnvVar=r.envVar||"",i&&(t.model=r.model||"")}function Ee(t){xe[t.uiLanguage]||(t.uiLanguage=y.uiLanguage),(!t.apiProvider||!k[t.apiProvider])&&(t.apiProvider="anthropic");let n=w(t);(!t.apiFormat||!x[t.apiFormat])&&(t.apiFormat=n.format),(!t.apiAuthType||!Pe[t.apiAuthType])&&(t.apiAuthType="auto"),t.backend==="anthropic-api"&&(t.apiProvider=t.apiProvider||"anthropic",t.apiFormat=t.apiFormat||"anthropic-messages",t.apiBaseUrl=t.apiBaseUrl||k.anthropic.baseUrl,t.apiAuthType=t.apiAuthType||"x-api-key",t.apiKeyEnvVar=t.apiKeyEnvVar||"ANTHROPIC_API_KEY");let e=Number(t.apiMaxTokens);(!Number.isFinite(e)||e<=0)&&(t.apiMaxTokens=y.apiMaxTokens);let r=Number(t.maxDocChars);return(!Number.isFinite(r)||r<1e3)&&(t.maxDocChars=y.maxDocChars),t.maxCacheEntries=et(t.maxCacheEntries),K[t.promptLanguage]||(t.promptLanguage=y.promptLanguage),t.minCards=We(t.minCards,y.minCards),t.maxCards=We(t.maxCards,y.maxCards),t.maxCardsYe(s)-Ye(l)||i.localeCompare(o)).slice(0,r.length-e).map(([i])=>i);for(let i of a)delete t[i];return a}function ie(t){let n=Ee(Object.assign({},y,t||{})),e=Se(n.backend),r=w(n),a=A(n),i=e?(n.apiBaseUrl||r.baseUrl||x[a]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return O(ve({cacheSchemaVersion:J,promptVersion:yt,maxDocChars:Number(n.maxDocChars)||y.maxDocChars,promptLanguage:n.promptLanguage,minCards:n.minCards,maxCards:n.maxCards,customSystemPromptHash:O(n.customSystemPrompt||""),backend:n.backend,model:n.model,apiProvider:e?n.apiProvider:"",apiFormat:e?a:"",apiBaseUrl:i,apiAuthType:e?Te(n):"",apiHeadersHash:e?O(n.apiHeaders||""):"",apiMaxTokens:e?Number(n.apiMaxTokens)||y.apiMaxTokens:0,structuredOutputVersion:1}))}function se(t,n,e){return!!t&&t.schemaVersion===J&&t.contentHash===O(n)&&t.settingsHash===ie(e)}function Ct(t){return t==="en"?"Write title, gist, and bullets in English.":t==="auto"?"Write title, gist, and bullets in the main language of the source document.":"\u7528\u4E2D\u6587\u8F93\u51FA title\u3001gist \u548C bullets\u3002"}function bt(t){return t==="en"?`{"cards":[ {"title":"U-shaped gains","anchor":"Who benefits from AI? Overall, it shifts the score from one to seven","gist":"AI productivity gains form a U shape, with both ends benefiting most","bullets":["Top-paid software managers benefit by accelerating existing work","Low-paid workers use AI to create new side income","Middle-layer specialists gain less because prompt precision is hard to trust","Average reported benefit is 5.1/7, with 42% describing gains as unclear"]} ]}`:`{"cards":[ {"title":"U \u578B\u6536\u76CA\u66F2\u7EBF","anchor":"\u90A3\u8C01\u53C8\u4F1A\u88AB AI \u6240\u53D7\u76CA\uFF1F\u6574\u4F53\u6765\u770B\uFF0C\u5B83\u628A\u6574\u4E2A\u5206\u6570\u53D8\u6210\u4E86\u4E00\u5206\u5230\u4E03\u5206","gist":"AI \u751F\u4EA7\u529B\u6536\u76CA\u5448 U \u578B\uFF0C\u4E24\u7AEF\u53D7\u76CA\u6700\u5927\u3001\u4E2D\u95F4\u5C42\u584C\u9677","bullets":["\u6700\u9AD8\u85AA\u5C97\u4F4D\uFF08\u8F6F\u4EF6\u7BA1\u7406\uFF09\u901A\u8FC7\u52A0\u901F\u65E2\u6709\u5DE5\u4F5C\u53D7\u76CA\u6700\u5927","\u6700\u4F4E\u85AA\u5C97\u4F4D\uFF08\u5916\u5356\u5458\u3001\u56ED\u827A\u5DE5\uFF09\u7528 AI \u5F00\u526F\u4E1A\u521B\u9020\u65B0\u6536\u5165","\u4E2D\u95F4\u5C42\u79D1\u5B66\u5BB6\u3001\u5F8B\u5E08\u6536\u76CA\u6700\u5C11\uFF0C\u90E8\u5206\u56E0\u5BF9 prompt \u7CBE\u5EA6\u4FE1\u4EFB\u4E0D\u8DB3","\u5168\u4F53\u5747\u5206 5.1/7\uFF0C42% \u62A5\u544A\u6536\u76CA\u6A21\u7CCA"]} -]}`}function xt(t,n){return String(t||"").replace(/\{([a-zA-Z0-9_]+)\}/g,(e,r)=>Object.hasOwn(n,r)?String(n[r]):e)}function Pt(t,n,e,r,a,i){return t==="en"?`You are a long-form reading summary assistant. After reading the full document, split it into ${n}-${e} natural topic units. They do not need to match markdown headings; use a complete argument or topic as the unit, merging short sections and splitting long ones when needed. +]}`}function wt(t,n){return String(t||"").replace(/\{([a-zA-Z0-9_]+)\}/g,(e,r)=>Object.hasOwn(n,r)?String(n[r]):e)}function vt(t,n,e,r,a,i){return t==="en"?`You are a long-form reading summary assistant. After reading the full document, split it into ${n}-${e} natural topic units. They do not need to match markdown headings; use a complete argument or topic as the unit, merging short sections and splitting long ones when needed. Each card has one guiding sentence plus several bullets. Bullets carry details; gist is the lead-in. @@ -73,7 +65,7 @@ ${i}`:`\u4F60\u662F\u4E00\u4E2A\u957F\u6587\u9605\u8BFB\u6458\u8981\u52A9\u624B\ ${a} \u793A\u4F8B\uFF1A -${i}`}function St(t,n,e,r,a){return t==="en"?`Non-overridable output contract: +${i}`}function At(t,n,e,r,a){return t==="en"?`Non-overridable output contract: - Must output ${n}-${e} cards. - ${r} - anchor must be copied verbatim from the source and exact-substring-match the source. @@ -83,20 +75,18 @@ ${i}`}function St(t,n,e,r,a){return t==="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: ${a}`}function Me(t,n){let e=Number(n.maxDocChars)||Te,r=q[n.promptLanguage]?n.promptLanguage:y.promptLanguage,a=Math.max(1,Number(n.minCards)||y.minCards),i=Math.max(a,Number(n.maxCards)||y.maxCards),s=vt(r),o=t.length>e?t.slice(0,e)+(r==="en"?` +- JSON shape: ${a}`}function ke(t,n){let e=Number(n.maxDocChars)||Ae,r=K[n.promptLanguage]?n.promptLanguage:y.promptLanguage,a=Math.max(1,Number(n.minCards)||y.minCards),i=Math.max(a,Number(n.maxCards)||y.maxCards),s=Ct(r),o=t.length>e?t.slice(0,e)+(r==="en"?` [Document truncated]`:` -[\u6587\u6863\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD]`):t,l='{"cards":[{"title":"...","anchor":"...","gist":"...","bullets":["...","..."]}]}',d=At(r),c={minCards:a,maxCards:i,languageInstruction:s,schema:l,example:d},p=xt(n.customSystemPrompt,c).trim(),h=St(r,a,i,s,l),f=Pt(r,a,i,s,l,d),u=p?`${p} +[\u6587\u6863\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD]`):t,l='{"cards":[{"title":"...","anchor":"...","gist":"...","bullets":["...","..."]}]}',p=bt(r),c={minCards:a,maxCards:i,languageInstruction:s,schema:l,example:p},u=wt(n.customSystemPrompt,c).trim(),h=At(r,a,i,s,l),C=vt(r,a,i,s,l,p),m=u?`${u} -${h}`:f,C=r==="en"?`Source document: +${h}`:C,f=r==="en"?`Source document: ${o}`:`\u4EE5\u4E0B\u662F\u9700\u8981\u5904\u7406\u7684\u6587\u6863\u5168\u6587\uFF1A -${o}`;return{system:u,user:C}}function Tt(t){return t.choices?.[0]?.delta?.content||""}function Et(t){return t.type==="content_block_delta"&&t.delta?.text||""}function U(t){switch(t){case"openai-chat":return Tt;case"anthropic-messages":return Et;default:return null}}function Ie(t,n){let e=[],r=t.split(` -`),a=r.pop();for(let i of r){let s=i.trim();if(!s||s==="data: [DONE]"||s.startsWith("event:")||!s.startsWith("data:"))continue;let o=s.slice(s.startsWith("data: ")?6:5);try{let l=JSON.parse(o),d=n(l);d&&e.push(d)}catch{}}return{deltas:e,rest:a}}async function Le(t,n,e,r,a,i,s){if(typeof globalThis.fetch!="function")throw new Error("Streaming requires fetch API");let o=await globalThis.fetch(t,{method:"POST",headers:n,body:JSON.stringify(e),signal:i});if(!o.ok){let h=await o.text();throw new Error(b(s||null,"errorProviderApiStatus",{label:"Streaming",status:o.status,excerpt:h.slice(0,500)}))}if(!o.body)throw new Error("Response has no body for streaming");let l=o.body.getReader(),d=new TextDecoder,c="",p="";try{for(;;){let{done:h,value:f}=await l.read();if(h)break;p+=d.decode(f,{stream:!0});let u=Ie(p,r);p=u.rest;for(let C of u.deltas)c+=C;u.deltas.length>0&&a?.({accumulated:c,done:!1})}}finally{l.releaseLock()}return a?.({accumulated:c,done:!0}),c}function X(t,n){let e=t.replace(/\/+$/,"");for(let r of n)if(e.endsWith(r))return e;return e+n[0]}function kt(t,n){let e=(t||"").trim();if(!e)return{};if(e.startsWith("{")){let a;try{a=JSON.parse(e)}catch(s){throw new Error(b(n||null,"errorCustomHeadersJsonParse",{error:s.message}))}if(!a||typeof a!="object"||Array.isArray(a))throw new Error(b(n||null,"errorCustomHeadersJsonObject"));let i={};for(let[s,o]of Object.entries(a))typeof o=="string"&&s.trim()&&(i[s.trim()]=o);return i}let r={};for(let a of e.split(/\r?\n/)){let i=a.trim();if(!i||i.startsWith("#"))continue;let s=i.indexOf(":");if(s<=0)throw new Error(b(n||null,"errorCustomHeadersLineFormat"));let o=i.slice(0,s).trim(),l=i.slice(s+1).trim();o&&(r[o]=l)}return r}function Ft(t){let n=Fe(t);if(n==="none")return{};let e=nt(t);if(!e){let r=(t.apiKeyEnvVar||w(t).envVar||"").trim(),a=r?b(t,"errorApiKeyEnvHint",{envVar:r}):"";throw new Error(b(t,"errorApiKeyMissing",{hint:a}))}return n==="bearer"?{authorization:`Bearer ${e}`}:n==="x-api-key"?{"x-api-key":e}:n==="x-goog-api-key"?{"x-goog-api-key":e}:n==="api-key"?{"api-key":e}:{authorization:`Bearer ${e}`}}function G(t,n){return{"content-type":"application/json",...Ft(t),...n||{},...kt(t.apiHeaders,t)}}function Nt(t,n,e){if(t.json&&typeof t.json=="object")return t.json;try{return JSON.parse(t.text||"{}")}catch{throw new Error(b(e||null,"errorProviderNonJson",{label:n,excerpt:(t.text||"").slice(0,500)}))}}async function it(t,n,e,r,a,i){let s;try{s=await t({url:e,method:"POST",headers:r,body:JSON.stringify(a),throw:!1})}catch(o){throw new Error(b(i||null,"errorProviderRequestFailed",{label:n,error:o.message||o}))}if(s.status>=400)throw new Error(b(i||null,"errorProviderApiStatus",{label:n,status:s.status,excerpt:(s.text||"").slice(0,500)}));return Nt(s,n,i)}function Dt(t){let n=String(t?.message?t.message:t);return/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(n)?/response_format|json_schema|responseJsonSchema|responseMimeType|tools?|tool_choice|unsupported|unrecognized|unknown|schema/i.test(n):!1}async function he(t,n,e,r,a,i,s){try{return await it(t,n,e,r,a,s)}catch(o){if(!i||!Dt(o))throw o;return console.warn(`[parallel-reader] ${n} structured output rejected; retrying without structured output`,o),it(t,n+" fallback",e,r,i,s)}}function Oe(t){return typeof t=="string"?t:Array.isArray(t)?t.map(n=>typeof n=="string"?n:n&&typeof n=="object"&&(n.text||n.output_text)||"").join(""):t&&typeof t=="object"&&(t.text||t.output_text)||""}function Rt(t){if(typeof t.output_text=="string")return t.output_text;let n=[],e=r=>{if(r&&typeof r!="string"){if(Array.isArray(r)){r.forEach(e);return}typeof r=="object"&&(typeof r.text=="string"&&n.push(r.text),typeof r.output_text=="string"&&n.push(r.output_text),r.type==="output_text"&&typeof r.content=="string"&&n.push(r.content),r.content&&e(r.content),r.output&&e(r.output))}};return e(t.output),n.join("")}function _e(t){let n=w(t),e=S[A(t)];return n.tokenLimitField||e?.tokenLimitField||"max_tokens"}function W(t,n,e,r){let a=!r||r.structured!==!1,i={model:L(e),max_tokens:Number(e.apiMaxTokens)||4096,system:t,messages:[{role:"user",content:n}]};return a&&(i.tools=[qe()],i.tool_choice={type:"tool",name:ie}),i}function Y(t,n,e,r){let a=!r||r.structured!==!1,i={model:L(e),messages:[{role:"system",content:t},{role:"user",content:n}]};return i[_e(e)]=Number(e.apiMaxTokens)||4096,a&&(i.response_format=$e()),i}function de(t,n,e,r){let a=!r||r.structured!==!1,i={model:L(e),instructions:t,input:n,max_output_tokens:Number(e.apiMaxTokens)||4096};return a&&(i.text=ze()),i}function pe(t,n,e,r){let a=!r||r.structured!==!1,i={temperature:0,maxOutputTokens:Number(e.apiMaxTokens)||4096};return a&&(i.responseMimeType="application/json",i.responseJsonSchema=J(!1)),{systemInstruction:{parts:[{text:t}]},contents:[{role:"user",parts:[{text:n}]}],generationConfig:i}}function Mt(t,n){let r=(Array.isArray(t?.content)?t.content:[]).find(a=>a&&a.type==="tool_use"&&a.name===ie);return r?typeof r.input=="string"?P(r.input,n):r.input&&typeof r.input=="object"?j(r.input):[]:null}async function It(t,n,e,r){let a=X(k(r),["/messages"]),i=await he(t,"Anthropic-compatible",a,G(r,{"anthropic-version":"2023-06-01"}),W(n,e,r),W(n,e,r,{structured:!1}),r),s=Mt(i,r);if(s)return s;let o=(i.content||[]).map(l=>Oe(l)).join("").trim();return P(o,r)}async function Lt(t,n,e,r){let a=X(k(r),["/chat/completions"]),s=((await he(t,"OpenAI-compatible Chat",a,G(r),Y(n,e,r),Y(n,e,r,{structured:!1}),r)).choices||[])[0]||{},o=Oe(s.message?.content||s.text||"").trim();return P(o,r)}async function Ot(t,n,e,r){let a=X(k(r),["/responses"]),i=await he(t,"OpenAI Responses",a,G(r),de(n,e,r),de(n,e,r,{structured:!1}),r);return P(Rt(i).trim(),r)}async function _t(t,n,e,r){let a=encodeURIComponent(L(r)),i=k(r);/:generateContent(?:\?|$)/.test(i)||(i=`${i.replace(/\/+$/,"")}/models/${a}:generateContent`);let s=G(r),c=((((await he(t,"Google Gemini",i,s,pe(n,e,r),pe(n,e,r,{structured:!1}),r)).candidates||[])[0]||{}).content?.parts||[]).map(p=>Oe(p)).join("").trim();return P(c,r)}async function me(t,n,e,r){switch(A(r)){case"openai-chat":return Lt(t,n,e,r);case"openai-responses":return Ot(t,n,e,r);case"google-generative-ai":return _t(t,n,e,r);default:return It(t,n,e,r)}}function Ve(t){if(!t.streaming)return!1;let n=A(t);return!!U(n)}async function Vt(t,n,e,r,a){let i=X(k(e),["/chat/completions"]),s=G(e),o=Y(t,n,e,{structured:!1});o.stream=!0;let l=U("openai-chat"),d=await Le(i,s,o,l,r,a,e);return P(d.trim(),e)}async function Bt(t,n,e,r,a){let i=X(k(e),["/messages"]),s=G(e,{"anthropic-version":"2023-06-01"}),o=W(t,n,e,{structured:!1});o.stream=!0;let l=U("anthropic-messages"),d=await Le(i,s,o,l,r,a,e);return P(d.trim(),e)}async function st(t,n,e,r,a){let i=A(e);switch(i){case"openai-chat":return Vt(t,n,e,r,a);case"anthropic-messages":return Bt(t,n,e,r,a);default:throw new Error(`Streaming not supported for format: ${i}`)}}async function ot(t,n){await me(t,'\u53EA\u8F93\u51FA JSON\uFF1A{"cards":[]}','\u8FDE\u901A\u6027\u6D4B\u8BD5\uFF1A\u8BF7\u539F\u6837\u8F93\u51FA {"cards":[]}',n);let e=A(n);return`${w(n).label} / ${S[e].label}`}function Be(t,n=80,e=.1){let r=Number(t?.top)||0,a=Math.max(0,Number(t?.height)||0),i=Math.min(Number(n)||0,a*e);return r+Math.max(0,i)}function Ut(t){return typeof requestAnimationFrame=="function"?requestAnimationFrame(t):setTimeout(()=>t(Date.now()),16)}function Gt(t){if(typeof cancelAnimationFrame=="function"){cancelAnimationFrame(t);return}clearTimeout(t)}function Ue(t,n=Ut,e=Gt){let r=null,a=()=>{r===null&&(r=n(()=>{r=null,t()}))};return a.cancel=()=>{r!==null&&(e(r),r=null)},a}var g=require("obsidian");async function Ht(t){if(t.backend==="codex"){let n=M("codex",t.cliPath),{stdout:e}=await K(n,["--version"],"",1e4);return`codex @ ${n} -${e.trim()}`}if(t.backend==="claude-code"){let n=M("claude",t.cliPath),{stdout:e}=await K(n,["--version"],"",1e4);return`claude @ ${n} -${e.trim()}`}if(I(t.backend))return ot(g.requestUrl,t);throw new Error("Unknown backend: "+t.backend)}var ge=class extends g.PluginSettingTab{constructor(n,e){super(n,e),this.plugin=e}display(){let{containerEl:n}=this,e=(i,s)=>this.plugin.t(i,s);n.empty(),n.createEl("h2",{text:e("settingsTitle")}),new g.Setting(n).setName(e("settingUiLanguageName")).setDesc(e("settingUiLanguageDesc")).addDropdown(i=>{for(let[s,o]of Object.entries(Ee))i.addOption(s,o);return i.setValue(this.plugin.settings.uiLanguage||y.uiLanguage).onChange(async s=>{this.plugin.settings.uiLanguage=s,await this.plugin.saveSettings(),this.display()})}),new g.Setting(n).setName(e("settingBackendName")).setDesc(e("settingBackendDesc")).addDropdown(i=>i.addOption("claude-code","Claude Code CLI").addOption("codex","Codex CLI").addOption("api","API / Provider").addOption("anthropic-api","Anthropic API (legacy)").setValue(this.plugin.settings.backend).onChange(async s=>{this.plugin.settings.backend=s,s==="api"&&!this.plugin.settings.apiBaseUrl&&Ne(this.plugin.settings,this.plugin.settings.apiProvider||"anthropic"),await this.plugin.saveSettings(),this.display()}));let r=I(this.plugin.settings.backend);if(!r)new g.Setting(n).setName(e("settingCliPathName")).setDesc(e("settingCliPathDesc")).addText(i=>i.setPlaceholder(e("settingCliPathPlaceholder")).setValue(this.plugin.settings.cliPath).onChange(async s=>{this.plugin.settings.cliPath=s.trim(),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingCliTimeoutName")).addText(i=>i.setValue(String(this.plugin.settings.cliTimeoutMs)).onChange(async s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.cliTimeoutMs=o,this.plugin.saveSettingsDebounced())}));else{n.createEl("h3",{text:e("apiProviderHeader")});let i=w(this.plugin.settings);new g.Setting(n).setName(e("settingProviderPresetName")).setDesc(e("settingProviderPresetDesc")).addDropdown(s=>{for(let[o,l]of Object.entries(F))s.addOption(o,l.label);return s.setValue(this.plugin.settings.apiProvider).onChange(async o=>{Ne(this.plugin.settings,o),await this.plugin.saveSettings(),this.display()})}),new g.Setting(n).setName(e("settingApiFormatName")).setDesc(e("settingApiFormatDesc")).addDropdown(s=>{for(let[o,l]of Object.entries(S))s.addOption(o,l.label);return s.setValue(A(this.plugin.settings)).onChange(async o=>{this.plugin.settings.apiFormat=o,await this.plugin.saveSettings(),this.display()})}),new g.Setting(n).setName(e("settingBaseUrlName")).setDesc(e("settingBaseUrlDesc")).addText(s=>s.setPlaceholder((this.plugin.settings.apiProvider||"").startsWith("custom-")?"https://your-provider.example/v1":i.baseUrl||S[A(this.plugin.settings)].defaultBaseUrl).setValue(this.plugin.settings.apiBaseUrl).onChange(async o=>{this.plugin.settings.apiBaseUrl=o.trim(),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingApiKeyName")).setDesc(e("settingApiKeyDesc")).addText(s=>(s.inputEl.type="password",s.setPlaceholder("sk-...").setValue(this.plugin.settings.apiKey).onChange(async o=>{this.plugin.settings.apiKey=o.trim(),this.plugin.saveSettingsDebounced()}))),new g.Setting(n).setName(e("settingApiKeyEnvName")).setDesc(e("settingApiKeyEnvDesc")).addText(s=>s.setPlaceholder(i.envVar||"OPENAI_API_KEY").setValue(this.plugin.settings.apiKeyEnvVar).onChange(async o=>{this.plugin.settings.apiKeyEnvVar=o.trim(),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingAuthTypeName")).setDesc(e("settingAuthTypeDesc")).addDropdown(s=>{for(let[o,l]of Object.entries(ke))s.addOption(o,l);return s.setValue(this.plugin.settings.apiAuthType||"auto").onChange(async o=>{this.plugin.settings.apiAuthType=o,await this.plugin.saveSettings()})}),new g.Setting(n).setName(e("settingHeadersName")).setDesc(e("settingHeadersDesc")).addTextArea(s=>s.setPlaceholder("cf-aig-authorization: Bearer ...").setValue(this.plugin.settings.apiHeaders).onChange(async o=>{this.plugin.settings.apiHeaders=o,this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingMaxTokensName")).addText(s=>s.setValue(String(this.plugin.settings.apiMaxTokens)).onChange(async o=>{let l=parseInt(o,10);!Number.isNaN(l)&&l>0&&(this.plugin.settings.apiMaxTokens=l,this.plugin.saveSettingsDebounced())})),new g.Setting(n).setName(e("settingStreamingName")).setDesc(e("settingStreamingDesc")).addToggle(s=>s.setValue(this.plugin.settings.streaming??!0).onChange(async o=>{this.plugin.settings.streaming=o,await this.plugin.saveSettings()}))}new g.Setting(n).setName(e("settingModelName")).setDesc(e(r?"settingModelDescApi":"settingModelDescCli")).addText(i=>i.setPlaceholder(r?w(this.plugin.settings).model||"model-id":y.model).setValue(this.plugin.settings.model).onChange(async s=>{this.plugin.settings.model=s.trim()||(r?"":y.model),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingMaxInputName")).setDesc(e("settingMaxInputDesc")).addText(i=>i.setValue(String(this.plugin.settings.maxDocChars||y.maxDocChars)).onChange(async s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>=1e3&&(this.plugin.settings.maxDocChars=o,this.plugin.saveSettingsDebounced())})),n.createEl("h3",{text:e("promptHeader")}),new g.Setting(n).setName(e("settingPromptLanguageName")).setDesc(e("settingPromptLanguageDesc")).addDropdown(i=>{for(let[s,o]of Object.entries(q))i.addOption(s,o);return i.setValue(this.plugin.settings.promptLanguage||y.promptLanguage).onChange(async s=>{this.plugin.settings.promptLanguage=s,await this.plugin.saveSettings()})}),new g.Setting(n).setName(e("settingCardRangeName")).setDesc(e("settingCardRangeDesc")).addText(i=>i.setPlaceholder("min").setValue(String(this.plugin.settings.minCards||y.minCards)).onChange(async s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.minCards=o,this.plugin.settings.maxCardsi.setPlaceholder("max").setValue(String(this.plugin.settings.maxCards||y.maxCards)).onChange(async s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.maxCards=Math.max(o,this.plugin.settings.minCards||y.minCards),this.plugin.saveSettingsDebounced())})),new g.Setting(n).setName(e("settingCustomPromptName")).setDesc(e("settingCustomPromptDesc")).addTextArea(i=>(i.inputEl.rows=8,i.setPlaceholder(e("settingCustomPromptPlaceholder")).setValue(this.plugin.settings.customSystemPrompt||"").onChange(async s=>{this.plugin.settings.customSystemPrompt=s,this.plugin.saveSettingsDebounced()}))),new g.Setting(n).setName(e("settingTestBackendName")).setDesc(e(r?"settingTestBackendDescApi":"settingTestBackendDescCli")).addButton(i=>i.setButtonText("Test").onClick(async()=>{try{let s=await Ht(this.plugin.settings);new g.Notice(`\u2713 ${s.slice(0,180)}`,8e3)}catch(s){new g.Notice(e("backendTestFailed",{error:s.message}),1e4)}})),new g.Setting(n).setName(e("settingExportFolderName")).setDesc(e("settingExportFolderDesc")).addText(i=>i.setValue(this.plugin.settings.exportFolder).onChange(async s=>{this.plugin.settings.exportFolder=s.trim()||y.exportFolder,this.plugin.saveSettingsDebounced()})),n.createEl("h3",{text:e("cacheHeader")}),new g.Setting(n).setName(e("settingMaxCacheName")).setDesc(e("settingMaxCacheDesc")).addText(i=>{i.setValue(String(this.plugin.settings.maxCacheEntries||B));let s=async()=>{let o=parseInt(i.getValue(),10);if(Number.isFinite(o)&&o>0){this.plugin.settings.maxCacheEntries=o,await this.plugin.saveSettings();let l=await this.plugin.pruneCacheIfNeeded();l.length>0&&new g.Notice(e("cachePruned",{count:l.length})),this.display()}};return i.inputEl.addEventListener("change",s),i.inputEl.addEventListener("keydown",o=>{o.key==="Enter"&&i.inputEl.blur()}),i});let a=Object.keys(this.plugin.cache).length;new g.Setting(n).setName(e("cachedNotesName",{count:a})).setDesc(e("cachedNotesDesc")).addButton(i=>i.setButtonText(e("clearAllCacheButton")).setWarning().onClick(async()=>{let s=Object.keys(this.plugin.cache).length;await this.plugin.cacheClear(),new g.Notice(e("cacheClearedAll",{count:s})),this.display()}))}};var E=require("obsidian");function N(t,n,e,r){let a=t.createEl("button",{cls:"parallel-reader-icon-button",attr:{type:"button","aria-label":e}});return a.title=e,typeof E.setIcon=="function"?(0,E.setIcon)(a,n):a.textContent=e,a.addEventListener("click",async i=>{i.preventDefault(),i.stopPropagation();try{await r()}catch(s){console.error(s),new E.Notice(`${e} failed: `+(s.message||s))}}),a}function D(t,n,e,r,a){let i=t.createEl("button",{cls:a||"parallel-reader-text-button",attr:{type:"button"}});return n&&typeof E.setIcon=="function"&&(0,E.setIcon)(i,n),i.createSpan({text:e}),i.addEventListener("click",async s=>{s.preventDefault(),s.stopPropagation();try{await r()}catch(o){console.error(o),new E.Notice(`${e} failed: `+(o.message||o))}}),i}async function O(t,n){try{await navigator.clipboard.writeText(t),new E.Notice(n)}catch(e){new E.Notice("Copy failed: "+(e.message||e))}}function Ge(t){return String(t||"").split("/").map(n=>n.trim()).filter(n=>!!n&&n!==".."&&n!==".").join("/")}function He(t){let n=Ge(t);if(!n)return[];let e=n.split("/");return e.map((r,a)=>e.slice(0,a+1).join("/"))}async function lt(t,n){for(let e of He(n))if(!t.vault.getAbstractFileByPath(e))try{await t.vault.createFolder(e)}catch(r){if(!t.vault.getAbstractFileByPath(e))throw r}}var v=require("obsidian");var ct=require("obsidian");var fe=class extends ct.Modal{constructor(n,e,r,a){super(n),this.plugin=e,this.card=r||{},this.onSave=a}onOpen(){let{contentEl:n}=this;n.empty(),n.createEl("h2",{text:this.plugin.t("editCardTitle")});let e=this.createLabeledInput(n,this.plugin.t("editCardTitleField"),this.card.title||""),r=this.createLabeledTextarea(n,this.plugin.t("editCardGistField"),this.card.gist||"",3),a=this.createLabeledTextarea(n,this.plugin.t("editCardBulletsField"),(this.card.bullets||[]).join(` -`),8),i=n.createDiv({cls:"parallel-reader-modal-actions"});D(i,null,this.plugin.t("editCardCancel"),()=>this.close(),"parallel-reader-text-button"),D(i,null,this.plugin.t("editCardSave"),async()=>{await this.onSave({title:e.value.trim()||this.card.title||"",gist:r.value.trim(),bullets:a.value.split(/\r?\n/).map(s=>s.trim()).filter(Boolean)}),this.close()},"parallel-reader-text-button")}createLabeledInput(n,e,r){let a=n.createDiv({cls:"parallel-reader-modal-field"});a.createEl("label",{text:e});let i=a.createEl("input",{attr:{type:"text"}});return i.value=r,i}createLabeledTextarea(n,e,r,a){let i=n.createDiv({cls:"parallel-reader-modal-field"});i.createEl("label",{text:e});let s=i.createEl("textarea");return s.rows=a,s.value=r,s}};var R="parallel-reader-view",ye=class extends v.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 R}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)}async loadFor(e,r,a){this.sourceFile=e,this.sections=r,this.stale=!!a,this.loadingMessage="",this.errorMessage="",this.render()}async 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 a=this.containerEl.children[1],i=a.querySelector(".parallel-reader-streaming-preview");if(i){let h=i.querySelector("pre");h&&(h.textContent=r.slice(-2e3));let f=i.querySelector(".parallel-reader-stream-counter");f&&(f.textContent=`${r.length} chars`);return}a.empty();let o=a.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"});N(l,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(e)});let d=a.createDiv({cls:"parallel-reader-state parallel-reader-loading parallel-reader-streaming-preview"});d.createDiv({cls:"parallel-reader-spinner"});let c=d.createEl("div",{cls:"parallel-reader-state-title"});c.createSpan({text:this.plugin.t("loadingGenerating")+" "}),c.createSpan({cls:"parallel-reader-stream-counter",text:`${r.length} chars`});let p=d.createEl("pre",{cls:"parallel-reader-stream-text"});p.textContent=r.slice(-2e3)}async 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 a=r.createDiv({cls:"parallel-reader-empty"});a.createEl("h3",{text:e.basename}),a.createEl("p",{text:this.plugin.t("emptyNoCache")}),a.createEl("code",{text:this.plugin.t("commandGenerate")})}render(){let e=this.containerEl.children[1];e.empty();let a=e.createDiv({cls:"parallel-reader-header"}).createDiv({cls:"parallel-reader-header-row"});a.createEl("div",{text:this.sourceFile?.basename||"",cls:"parallel-reader-title"});let i=a.createDiv({cls:"parallel-reader-actions"});if(this.sourceFile&&(this.plugin.isGeneratingFile(this.sourceFile)?N(i,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(this.sourceFile)}):N(i,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0)),N(i,"copy",this.plugin.t("actionCopyAll"),()=>this.plugin.copyCurrentViewMarkdown()),N(i,"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")}),D(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"}),D(l,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-text-button");return}let s=e.createDiv({cls:"parallel-reader-cards"});this.cards=[];let o=this.sourceFile?.path||"";this.sections.forEach((l,d)=>{let c=s.createDiv({cls:"parallel-reader-card"});c.dataset.idx=String(d),l.startLine<0&&c.addClass("parallel-reader-card-unanchored");let p=c.createEl("div",{cls:"parallel-reader-card-title"});if(p.createSpan({text:l.title}),l.startLine<0&&p.createEl("span",{text:" \u26A0",cls:"parallel-reader-warn",title:this.plugin.t("anchorMismatch")}),l.gist){let f=c.createEl("div",{cls:"parallel-reader-gist"});v.MarkdownRenderer.render(this.app,l.gist,f,o,this).catch(()=>{f.setText(l.gist)})}let h=l.bullets||[];if(h.length>0){let f=c.createEl("div",{cls:"parallel-reader-bullets-md"}),u=h.map(C=>`- ${C}`).join(` -`);v.MarkdownRenderer.render(this.app,u,f,o,this).catch(()=>{f.setText(u)})}else l.gist||c.createEl("div",{cls:"parallel-reader-empty-li",text:this.plugin.t("emptyCard")});c.addEventListener("click",f=>{let u=window.getSelection();if(u&&u.toString().length>0)return;let C=f.target;C&&C.tagName==="A"||l.startLine>=0&&this.plugin.scrollEditorToLine(l.startLine,this.sourceFile)}),c.addEventListener("contextmenu",f=>{f.preventDefault();let u=new v.Menu;u.addItem(C=>C.setTitle(this.plugin.t("menuCopyMarkdown")).setIcon("copy").onClick(()=>O(Pe(l),this.plugin.t("copiedMarkdown")))),u.addItem(C=>C.setTitle(this.plugin.t("menuCopyPlain")).setIcon("clipboard-copy").onClick(()=>O(Qe(l),this.plugin.t("copiedPlain")))),l.anchor&&u.addItem(C=>C.setTitle(this.plugin.t("menuCopyAnchor")).setIcon("quote-glyph").onClick(()=>O(l.anchor,this.plugin.t("copiedAnchor")))),u.addSeparator(),l.startLine>=0&&u.addItem(C=>C.setTitle(this.plugin.t("menuJumpSource")).setIcon("arrow-right").onClick(()=>this.plugin.scrollEditorToLine(l.startLine,this.sourceFile))),u.addSeparator(),u.addItem(C=>C.setTitle(this.plugin.t("menuEditCard")).setIcon("pencil").onClick(()=>this.openEditCardModal(d))),u.addItem(C=>C.setTitle(this.plugin.t("menuDeleteCard")).setIcon("trash").onClick(()=>this.deleteCard(d))),u.showAtMouseEvent(f)}),this.cards.push(c)}),this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].addClass("is-active")}setActiveSection(e){e!==this.activeIdx&&(this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].removeClass("is-active"),this.activeIdx=e,e>=0&&this.cards[e]&&(this.cards[e].addClass("is-active"),this.cards[e].scrollIntoView({block:"nearest",behavior:"smooth"})))}moveActiveSection(e){let r=oe(this.activeIdx,this.sections.length,e);return this.setActiveSection(r),this.focusSummaryPane(),r}jumpToActiveSection(){let e=le(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=te(this.sections,e);if(r.length===this.sections.length)return!1;let a=this.sections.length;return this.sections=r,this.activeIdx=re(e,a,this.activeIdx),await this.plugin.cacheReplaceCards(this.sourceFile.path,r),this.render(),new v.Notice(this.plugin.t("cardDeleted")),!0}openEditCardModal(e){return!this.sourceFile||!this.sections[e]?!1:(new fe(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 a=ne(this.sections,e,r);return a.length!==this.sections.length?!1:(this.sections=a,await this.plugin.cacheReplaceCards(this.sourceFile.path,a),this.render(),new v.Notice(this.plugin.t("cardSaved")),!0)}async exportToVault(){if(!this.sourceFile)return;let e=Ge(this.plugin.settings.exportFolder),r=`${this.sourceFile.basename} - ${this.plugin.t("displayName")}.md`,a=`${e}/${r}`,i=["---",`source: [[${this.sourceFile.basename}]]`,`generated: ${new Date().toISOString().slice(0,10)}`,"tool: parallel-reader","---","",$(`${this.sourceFile.basename} \xB7 ${this.plugin.t("displayName")}`,this.sections),""].join(` -`),s=this.plugin.app;await lt(s,e);let o=s.vault.getAbstractFileByPath(a);o instanceof v.TFile?await s.vault.modify(o,i):await s.vault.create(a,i),new v.Notice(this.plugin.t("exported",{path:a}))}};async function jt(t,n,e,r){let{system:a,user:i}=Me(t,n),s,o=I(n.backend)&&Ve(n),l=o?new AbortController:null;switch(l&&e.onCancel(()=>l.abort()),n.backend){case"codex":s=await Ze(a,i,n,e);break;case"api":case"anthropic-api":o?s=await st(a,i,n,r,l.signal):s=await me(m.requestUrl,a,i,n);break;default:s=await Xe(a,i,n,e);break}let d=s.map(c=>({title:c.title,level:2,anchor:c.anchor,gist:c.gist,startLine:ee(t,c.anchor),bullets:c.bullets}));return d.sort((c,p)=>c.startLine<0&&p.startLine<0?0:c.startLine<0?1:p.startLine<0?-1:c.startLine-p.startLine),d}function ut(t,n){return n?.phase==="generating"&&t&&I(t.backend)?"cancelRequestedApiInFlight":"cancelRequested"}var je=class extends m.Plugin{constructor(){super(...arguments);this._scrollDispose=null;this._settingsSaveTimer=null;this._cacheSaveTimer=null;this._cacheDirty=!1}t(e,r){return b(this.settings||y,e,r)}async onload(){await this.loadSettings(),this.jobs=new H,this._cacheSaveTimer=null,this._cacheDirty=!1,this.addRibbonIcon("book-open",this.t("ribbonOpen"),async()=>{let e=this.getActiveView();await this.ensureView(),e?.file&&await this.syncViewToFile(e.file)}),this.registerView(R,e=>new ye(e,this)),this.addCommand({id:"parallel-reader-run",name:this.t("cmdRun"),callback:()=>this.runForActiveFile(!1)}),this.addCommand({id:"parallel-reader-regen",name:this.t("cmdRegen"),callback:()=>this.runForActiveFile(!0)}),this.addCommand({id:"parallel-reader-open-view",name:this.t("cmdOpenView"),callback:async()=>{let e=this.getActiveView();await this.ensureView(),e?.file&&this.syncViewToFile(e.file)}}),this.addCommand({id:"parallel-reader-export-current",name:this.t("cmdExport"),callback:()=>this.exportCurrentView()}),this.addCommand({id:"parallel-reader-copy-current-markdown",name:this.t("cmdCopyMarkdown"),callback:()=>this.copyCurrentViewMarkdown()}),this.addCommand({id:"parallel-reader-cancel-current",name:this.t("cmdCancel"),callback:()=>this.cancelActiveGeneration()}),this.addCommand({id:"parallel-reader-clear-current",name:this.t("cmdClearCurrent"),callback:async()=>{let e=this.getActiveView();if(!e?.file)return new m.Notice(this.t("noCurrentNote"));await this.cacheDelete(e.file.path),new m.Notice(this.t("cacheClearedFile",{name:e.file.basename}))}}),this.addCommand({id:"parallel-reader-clear-all",name:this.t("cmdClearAll"),callback:async()=>{let e=Object.keys(this.cache).length;await this.cacheClear(),new m.Notice(this.t("cacheClearedAll",{count:e}))}}),this.addCommand({id:"parallel-reader-card-prev",name:this.t("cmdCardPrev"),hotkeys:[{modifiers:["Alt"],key:"ArrowUp"}],callback:()=>this.moveActiveCard(-1)}),this.addCommand({id:"parallel-reader-card-next",name:this.t("cmdCardNext"),hotkeys:[{modifiers:["Alt"],key:"ArrowDown"}],callback:()=>this.moveActiveCard(1)}),this.addCommand({id:"parallel-reader-card-jump",name:this.t("cmdCardJump"),callback:()=>this.jumpActiveCard()}),this.addSettingTab(new ge(this.app,this)),this.registerEvent(this.app.workspace.on("active-leaf-change",()=>this.bindScrollSync())),this.registerEvent(this.app.workspace.on("file-open",e=>{e&&this.syncViewToFile(e)})),this.registerEvent(this.app.workspace.on("file-menu",(e,r)=>this.addFileMenuItems(e,r))),this.registerEvent(this.app.vault.on("rename",(e,r)=>this.handleFileRename(e,r))),this.registerEvent(this.app.vault.on("delete",e=>this.handleFileDelete(e))),this.bindScrollSync()}async onunload(){await this.flushSettingsSave(),await this.flushCacheSave(),this.app.workspace.detachLeavesOfType(R)}async loadSettings(){let r=(await this.loadData()||{}).settings||{};this.settings=De(Object.assign({},y,r)),await this.loadCache()}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())}async saveCache(){this._cacheSaveTimer&&(clearTimeout(this._cacheSaveTimer),this._cacheSaveTimer=null),this.pruneCache(),await this.writeCacheFile(),this._cacheDirty=!1}scheduleCacheSave(e=5e3){this._cacheDirty=!0,!this._cacheSaveTimer&&(this._cacheSaveTimer=setTimeout(()=>{this._cacheSaveTimer=null,this._cacheDirty&&this.saveCache().catch(r=>console.error("[parallel-reader] failed to save cache",r))},e))}async flushCacheSave(){this._cacheSaveTimer&&(clearTimeout(this._cacheSaveTimer),this._cacheSaveTimer=null),this._cacheDirty&&await this.saveCache()}cacheFilePath(){let e=this.app.vault.configDir||".obsidian",r=this.manifest?.id||"parallel-reader";return`${e}/plugins/${r}/cache.json`}async ensurePluginDataDir(){let e=this.app.vault.adapter,r=this.app.vault.configDir||".obsidian",a=this.manifest?.id||"parallel-reader",i=`${r}/plugins/${a}`;try{if(typeof e.exists=="function"&&await e.exists(i))return;await e.mkdir(i)}catch{}}async readCacheFile(){let e=this.app.vault.adapter;try{let r=await e.read(this.cacheFilePath()),a=JSON.parse(r);if(a&&typeof a=="object"&&a.entries&&typeof a.entries=="object")return a.entries}catch(r){let a=String(r?.message||r||"");/not found|does not exist|ENOENT/i.test(a)||console.warn("[parallel-reader] failed to read cache.json",r)}return{}}async writeCacheFile(){await this.ensurePluginDataDir(),await this.app.vault.adapter.write(this.cacheFilePath(),be(this.cache))}async loadCache(){this.cache=await this.readCacheFile(),this.pruneCache().length>0&&await this.writeCacheFile()}pruneCache(){return Re(this.cache,this.settings?.maxCacheEntries||B)}async pruneCacheIfNeeded(){let e=this.pruneCache();return e.length>0&&await this.saveCache(),e}cacheGet(e){return this.cache[e]||null}async cacheTouch(e){let r=Ce(this.cache[e]||null);return r?(this.scheduleCacheSave(),r):null}async cachePut(e,r,a,i){let s=new Date().toISOString();this.cache[e]={schemaVersion:z,contentHash:V(r),settingsHash:ce(i||this.settings),cards:a,generatedAt:s,lastAccessedAt:s},await this.saveCache()}async cacheReplaceCards(e,r){let a=this.cache[e];if(!a)return!1;let i=new Date().toISOString();return a.cards=(r||[]).map(s=>({title:s.title,anchor:s.anchor,gist:s.gist,bullets:s.bullets||[]})),a.updatedAt=i,a.lastAccessedAt=i,await this.saveCache(),!0}async cacheDelete(e){this.cache[e]&&(delete this.cache[e],await this.saveCache())}async cacheClear(){this.cache={},await this.saveCache()}async ensureView(){let{workspace:e}=this.app,r=e.getLeavesOfType(R)[0];return r||(r=e.getRightLeaf(!1),await r.setViewState({type:R,active:!0})),e.revealLeaf(r),r.view}getActiveView(){return this.app.workspace.getActiveViewOfType(m.MarkdownView)}getParallelView(){return this.app.workspace.getLeavesOfType(R)[0]?.view}moveActiveCard(e){let r=this.getParallelView();return r?.sections.length?r.moveActiveSection(e):(new m.Notice(this.t("noActiveCard")),-1)}jumpActiveCard(){let e=this.getParallelView();return!e||e.jumpToActiveSection()<0?(new m.Notice(this.t("noActiveCard")),!1):!0}isGeneratingFile(e){return!!e&&!!e.path&&this.jobs.isRunning(e.path)}cancelGenerationForFile(e){if(!e?.path)return new m.Notice(this.t("noCancelableJob")),!1;let r=this.jobs.get(e.path),a=ut(this.settings,r),i=this.jobs.cancel(e.path);return new m.Notice(i?this.t(a):this.t("noCancelableJob")),i}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(){let e=this.getActiveView();if(e?.file&&this.cancelGenerationForFile(e.file))return;let r=this.getParallelView();r?.sourceFile?this.cancelGenerationForFile(r.sourceFile):new m.Notice(this.t("noCancelableJob"))}confirmRegenerateEditedCards(){let e=this.t("confirmRegenerateEditedCards");return typeof window<"u"&&typeof window.confirm=="function"?window.confirm(e):!0}addFileMenuItems(e,r){!(r instanceof m.TFile)||!r.path.endsWith(".md")||(e.addSeparator(),e.addItem(a=>a.setTitle(this.t("fileMenuGenerate")).setIcon("book-open").onClick(()=>this.runForFile(r,!1))),e.addItem(a=>a.setTitle(this.t("fileMenuRegen")).setIcon("refresh-cw").onClick(()=>this.runForFile(r,!0))),this.cacheGet(r.path)&&e.addItem(a=>a.setTitle(this.t("fileMenuClear")).setIcon("trash").onClick(async()=>{await this.cacheDelete(r.path),new m.Notice(this.t("cacheClearedFile",{name:r.basename}))})))}async handleFileRename(e,r){if(!(e instanceof m.TFile)||!r)return;let a=r.endsWith(".md"),i=e.path.endsWith(".md");if(!a&&!i)return;if(a&&!i){this.cache[r]&&(delete this.cache[r],await this.saveCache());let o=this.getParallelView();o&&o.sourceFile?.path===r&&o.renderEmpty();return}if(!a)return;this.cache[r]&&(this.cache[e.path]=this.cache[r],delete this.cache[r],await this.saveCache());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 m.TFile))return;this.cache[e.path]&&(delete this.cache[e.path],await this.saveCache());let r=this.getParallelView();r?.sourceFile?.path===e.path&&r.renderEmpty()}async exportCurrentView(){let e=this.getActiveView(),r=await this.ensureView();if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new m.Notice(this.t("noExportContent"));return}await r.exportToVault()}async copyCurrentViewMarkdown(){let e=this.getActiveView(),r=await this.ensureView();if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new m.Notice(this.t("noCopyContent"));return}await O($(`${r.sourceFile.basename} \xB7 ${this.t("displayName")}`,r.sections),this.t("copiedAllMarkdown"))}async runForActiveFile(e){let r=this.getActiveView();if(!r?.file){new m.Notice(this.t("openNoteFirst"));return}return this.runForFile(r.file,e)}async runForFile(e,r){if(!e){new m.Notice(this.t("openNoteFirst"));return}if(this.jobs.isRunning(e.path)){new m.Notice(this.t("alreadyGenerating"));return}if(we(this.cacheGet(e.path),r)&&!this.confirmRegenerateEditedCards()){new m.Notice(this.t("regenerateCancelled"));return}let a=null;return this.jobs.start(e.path,async i=>{i.setPhase("reading");let s=await this.app.vault.read(e);if(i.throwIfCancelled(),!s.trim()){new m.Notice(this.t("emptyNote"));return}if(a=await this.ensureView(),i.throwIfCancelled(),i.setPhase("cache-check"),!r){let h=this.cacheGet(e.path);if(ue(h,s,this.settings)){await this.cacheTouch(e.path),this.activeFileStillMatches(e)&&await a.loadFor(e,this.resolveCardAnchors(s,h.cards),!1);return}}await a.renderLoading(e,this.t("loadingGenerating"));let o=Number(this.settings.maxDocChars)||y.maxDocChars;s.length>o&&new m.Notice(this.t("longNoteTruncated",{count:o})),new m.Notice(this.t("generatingNotice")),i.setPhase("generating");let l=a?h=>{!h.done&&this.viewIsShowingFile(a,e)&&a.renderStreamingPreview(e,h.accumulated)}:void 0,d=await jt(s,this.settings,i,l);if(i.throwIfCancelled(),d.length===0){new m.Notice(this.t("noCardsReturned"));return}let c=d.map(h=>({title:h.title,anchor:h.anchor,gist:h.gist,bullets:h.bullets}));i.setPhase("saving"),await this.cachePut(e.path,s,c,this.settings),i.throwIfCancelled(),this.viewIsShowingFile(a,e)&&await a.loadFor(e,d,!1);let p=d.filter(h=>h.startLine<0).length;new m.Notice(this.t("generationDone",{count:d.length,suffix:p?this.t("unanchoredSuffix",{count:p}):""}))}).catch(async i=>{if(i instanceof _){new m.Notice(i.message);return}if(i instanceof x){a&&this.viewIsShowingFile(a,e)&&await a.renderError(e,this.t("cancelledError")),new m.Notice(this.t("cancelled"));return}let s=Ae(i);console.error(i),a&&this.viewIsShowingFile(a,e)&&await a.renderError(e,i.message||String(i)),new m.Notice(this.t("generationFailed",{kind:s==="unknown"?"":` (${s})`,error:i.message||i}))})}resolveCardAnchors(e,r){let a=(r||[]).map(i=>({title:i.title,level:2,anchor:i.anchor,gist:i.gist,startLine:ee(e,i.anchor),bullets:i.bullets||[]}));return a.sort((i,s)=>i.startLine<0&&s.startLine<0?0:i.startLine<0?1:s.startLine<0?-1:i.startLine-s.startLine),a}async syncViewToFile(e){if(!e?.path?.endsWith(".md"))return;let r=this.app.workspace.getLeavesOfType(R);if(r.length===0)return;let a=r[0].view;if(!a||!this.activeFileStillMatches(e))return;let i=this.cacheGet(e.path);if(!i){await a.loadFor(e,[],!1),a.renderEmptyWithHint(e);return}let s=await this.app.vault.read(e);if(!this.activeFileStillMatches(e))return;let o=!ue(i,s,this.settings);await this.cacheTouch(e.path),await a.loadFor(e,this.resolveCardAnchors(s,i.cards),o)}bindScrollSync(){this._scrollDispose&&(this._scrollDispose(),this._scrollDispose=null);let e=this.getActiveView();if(!e)return;let r=e.editor,a=r&&r.cm,i=a?.scrollDOM?a.scrollDOM:e.contentEl.querySelector(".cm-scroller");if(!i)return;let s=Ue(()=>this.handleEditorScroll(e));i.addEventListener("scroll",s,{passive:!0}),this._scrollDispose=()=>{s.cancel(),i.removeEventListener("scroll",s)}}handleEditorScroll(e){let r=this.getParallelView();if(!r||!e.file||r.sourceFile?.path!==e.file.path)return;let a=e.editor,i=a&&a.cm;if(!i?.scrollDOM)return;let s=i.scrollDOM.getBoundingClientRect(),o=Be(s),l=0;try{let c=i.posAtCoords({x:s.left+20,y:o});c!=null&&(l=i.state.doc.lineAt(c).number-1)}catch{return}let d=-1;for(let c=0;c0&&a?.({accumulated:c,done:!1})}}finally{l.releaseLock()}return a?.({accumulated:c,done:!0}),c}function q(t,n){let e=t.replace(/\/+$/,"");for(let r of n)if(e.endsWith(r))return e;return e+n[0]}function St(t,n){let e=(t||"").trim();if(!e)return{};if(e.startsWith("{")){let a;try{a=JSON.parse(e)}catch(s){throw new Error(b(n||null,"errorCustomHeadersJsonParse",{error:s.message}))}if(!a||typeof a!="object"||Array.isArray(a))throw new Error(b(n||null,"errorCustomHeadersJsonObject"));let i={};for(let[s,o]of Object.entries(a))typeof o=="string"&&s.trim()&&(i[s.trim()]=o);return i}let r={};for(let a of e.split(/\r?\n/)){let i=a.trim();if(!i||i.startsWith("#"))continue;let s=i.indexOf(":");if(s<=0)throw new Error(b(n||null,"errorCustomHeadersLineFormat"));let o=i.slice(0,s).trim(),l=i.slice(s+1).trim();o&&(r[o]=l)}return r}function Tt(t){let n=Te(t);if(n==="none")return{};let e=Ze(t);if(!e){let r=(t.apiKeyEnvVar||w(t).envVar||"").trim(),a=r?b(t,"errorApiKeyEnvHint",{envVar:r}):"";throw new Error(b(t,"errorApiKeyMissing",{hint:a}))}return n==="bearer"?{authorization:`Bearer ${e}`}:n==="x-api-key"?{"x-api-key":e}:n==="x-goog-api-key"?{"x-goog-api-key":e}:n==="api-key"?{"api-key":e}:{authorization:`Bearer ${e}`}}function B(t,n){return{"content-type":"application/json",...Tt(t),...n||{},...St(t.apiHeaders,t)}}function Et(t,n,e){if(t.json&&typeof t.json=="object")return t.json;try{return JSON.parse(t.text||"{}")}catch{throw new Error(b(e||null,"errorProviderNonJson",{label:n,excerpt:(t.text||"").slice(0,500)}))}}async function tt(t,n,e,r,a,i){let s;try{s=await t({url:e,method:"POST",headers:r,body:JSON.stringify(a),throw:!1})}catch(o){throw new Error(b(i||null,"errorProviderRequestFailed",{label:n,error:o.message||o}))}if(s.status>=400)throw new Error(b(i||null,"errorProviderApiStatus",{label:n,status:s.status,excerpt:(s.text||"").slice(0,500)}));return Et(s,n,i)}function Ft(t){let n=String(t?.message?t.message:t);return/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(n)?/response_format|json_schema|responseJsonSchema|responseMimeType|tools?|tool_choice|unsupported|unrecognized|unknown|schema/i.test(n):!1}async function ce(t,n,e,r,a,i,s){try{return await tt(t,n,e,r,a,s)}catch(o){if(!i||!Ft(o))throw o;return console.warn(`[parallel-reader] ${n} structured output rejected; retrying without structured output`,o),tt(t,n+" fallback",e,r,i,s)}}function Re(t){return typeof t=="string"?t:Array.isArray(t)?t.map(n=>typeof n=="string"?n:n&&typeof n=="object"&&(n.text||n.output_text)||"").join(""):t&&typeof t=="object"&&(t.text||t.output_text)||""}function kt(t){if(typeof t.output_text=="string")return t.output_text;let n=[],e=r=>{if(r&&typeof r!="string"){if(Array.isArray(r)){r.forEach(e);return}typeof r=="object"&&(typeof r.text=="string"&&n.push(r.text),typeof r.output_text=="string"&&n.push(r.output_text),r.type==="output_text"&&typeof r.content=="string"&&n.push(r.content),r.content&&e(r.content),r.output&&e(r.output))}};return e(t.output),n.join("")}function Me(t){let n=w(t),e=x[A(t)];return n.tokenLimitField||e?.tokenLimitField||"max_tokens"}function $(t,n,e,r){let a=!r||r.structured!==!1,i={model:M(e),max_tokens:Number(e.apiMaxTokens)||4096,system:t,messages:[{role:"user",content:n}]};return a&&(i.tools=[Je()],i.tool_choice={type:"tool",name:re}),i}function z(t,n,e,r){let a=!r||r.structured!==!1,i={model:M(e),messages:[{role:"system",content:t},{role:"user",content:n}]};return i[Me(e)]=Number(e.apiMaxTokens)||4096,a&&(i.response_format=Ge()),i}function oe(t,n,e,r){let a=!r||r.structured!==!1,i={model:M(e),instructions:t,input:n,max_output_tokens:Number(e.apiMaxTokens)||4096};return a&&(i.text=He()),i}function le(t,n,e,r){let a=!r||r.structured!==!1,i={temperature:0,maxOutputTokens:Number(e.apiMaxTokens)||4096};return a&&(i.responseMimeType="application/json",i.responseJsonSchema=G(!1)),{systemInstruction:{parts:[{text:t}]},contents:[{role:"user",parts:[{text:n}]}],generationConfig:i}}function Nt(t,n){let r=(Array.isArray(t?.content)?t.content:[]).find(a=>a&&a.type==="tool_use"&&a.name===re);return r?typeof r.input=="string"?T(r.input,n):r.input&&typeof r.input=="object"?j(r.input):[]:null}async function Dt(t,n,e,r){let a=q(E(r),["/messages"]),i=await ce(t,"Anthropic-compatible",a,B(r,{"anthropic-version":"2023-06-01"}),$(n,e,r),$(n,e,r,{structured:!1}),r),s=Nt(i,r);if(s)return s;let o=(i.content||[]).map(l=>Re(l)).join("").trim();return T(o,r)}async function Rt(t,n,e,r){let a=q(E(r),["/chat/completions"]),s=((await ce(t,"OpenAI-compatible Chat",a,B(r),z(n,e,r),z(n,e,r,{structured:!1}),r)).choices||[])[0]||{},o=Re(s.message?.content||s.text||"").trim();return T(o,r)}async function Mt(t,n,e,r){let a=q(E(r),["/responses"]),i=await ce(t,"OpenAI Responses",a,B(r),oe(n,e,r),oe(n,e,r,{structured:!1}),r);return T(kt(i).trim(),r)}async function It(t,n,e,r){let a=encodeURIComponent(M(r)),i=E(r);/:generateContent(?:\?|$)/.test(i)||(i=`${i.replace(/\/+$/,"")}/models/${a}:generateContent`);let s=B(r),c=((((await ce(t,"Google Gemini",i,s,le(n,e,r),le(n,e,r,{structured:!1}),r)).candidates||[])[0]||{}).content?.parts||[]).map(u=>Re(u)).join("").trim();return T(c,r)}async function ue(t,n,e,r){switch(A(r)){case"openai-chat":return Rt(t,n,e,r);case"openai-responses":return Mt(t,n,e,r);case"google-generative-ai":return It(t,n,e,r);default:return Dt(t,n,e,r)}}function Ie(t){if(!t.streaming)return!1;let n=A(t);return!!V(n)}async function Lt(t,n,e,r,a){let i=q(E(e),["/chat/completions"]),s=B(e),o=z(t,n,e,{structured:!1});o.stream=!0;let l=V("openai-chat"),p=await De(i,s,o,l,r,a,e);return T(p.trim(),e)}async function Ot(t,n,e,r,a){let i=q(E(e),["/messages"]),s=B(e,{"anthropic-version":"2023-06-01"}),o=$(t,n,e,{structured:!1});o.stream=!0;let l=V("anthropic-messages"),p=await De(i,s,o,l,r,a,e);return T(p.trim(),e)}async function rt(t,n,e,r,a){let i=A(e);switch(i){case"openai-chat":return Lt(t,n,e,r,a);case"anthropic-messages":return Ot(t,n,e,r,a);default:throw new Error(`Streaming not supported for format: ${i}`)}}async function nt(t,n){await ue(t,'\u53EA\u8F93\u51FA JSON\uFF1A{"cards":[]}','\u8FDE\u901A\u6027\u6D4B\u8BD5\uFF1A\u8BF7\u539F\u6837\u8F93\u51FA {"cards":[]}',n);let e=A(n);return`${w(n).label} / ${x[e].label}`}function Le(t,n=80,e=.1){let r=Number(t?.top)||0,a=Math.max(0,Number(t?.height)||0),i=Math.min(Number(n)||0,a*e);return r+Math.max(0,i)}function _t(t){return typeof requestAnimationFrame=="function"?requestAnimationFrame(t):setTimeout(()=>t(Date.now()),16)}function Vt(t){if(typeof cancelAnimationFrame=="function"){cancelAnimationFrame(t);return}clearTimeout(t)}function Oe(t,n=_t,e=Vt){let r=null,a=()=>{r===null&&(r=n(()=>{r=null,t()}))};return a.cancel=()=>{r!==null&&(e(r),r=null)},a}var g=require("obsidian");async function Bt(t){return nt(g.requestUrl,t)}var pe=class extends g.PluginSettingTab{constructor(n,e){super(n,e),this.plugin=e}display(){let{containerEl:n}=this,e=(a,i)=>this.plugin.t(a,i);n.empty(),n.createEl("h2",{text:e("settingsTitle")}),new g.Setting(n).setName(e("settingUiLanguageName")).setDesc(e("settingUiLanguageDesc")).addDropdown(a=>{for(let[i,s]of Object.entries(xe))a.addOption(i,s);return a.setValue(this.plugin.settings.uiLanguage||y.uiLanguage).onChange(async i=>{this.plugin.settings.uiLanguage=i,await this.plugin.saveSettings(),this.display()})}),n.createEl("h3",{text:e("apiProviderHeader")});{let a=w(this.plugin.settings);new g.Setting(n).setName(e("settingProviderPresetName")).setDesc(e("settingProviderPresetDesc")).addDropdown(i=>{for(let[s,o]of Object.entries(k))i.addOption(s,o.label);return i.setValue(this.plugin.settings.apiProvider).onChange(async s=>{Qe(this.plugin.settings,s),await this.plugin.saveSettings(),this.display()})}),new g.Setting(n).setName(e("settingApiFormatName")).setDesc(e("settingApiFormatDesc")).addDropdown(i=>{for(let[s,o]of Object.entries(x))i.addOption(s,o.label);return i.setValue(A(this.plugin.settings)).onChange(async s=>{this.plugin.settings.apiFormat=s,await this.plugin.saveSettings(),this.display()})}),new g.Setting(n).setName(e("settingBaseUrlName")).setDesc(e("settingBaseUrlDesc")).addText(i=>i.setPlaceholder((this.plugin.settings.apiProvider||"").startsWith("custom-")?"https://your-provider.example/v1":a.baseUrl||x[A(this.plugin.settings)].defaultBaseUrl).setValue(this.plugin.settings.apiBaseUrl).onChange(async s=>{this.plugin.settings.apiBaseUrl=s.trim(),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingApiKeyName")).setDesc(e("settingApiKeyDesc")).addText(i=>(i.inputEl.type="password",i.setPlaceholder("sk-...").setValue(this.plugin.settings.apiKey).onChange(async s=>{this.plugin.settings.apiKey=s.trim(),this.plugin.saveSettingsDebounced()}))),new g.Setting(n).setName(e("settingApiKeyEnvName")).setDesc(e("settingApiKeyEnvDesc")).addText(i=>i.setPlaceholder(a.envVar||"OPENAI_API_KEY").setValue(this.plugin.settings.apiKeyEnvVar).onChange(async s=>{this.plugin.settings.apiKeyEnvVar=s.trim(),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingAuthTypeName")).setDesc(e("settingAuthTypeDesc")).addDropdown(i=>{for(let[s,o]of Object.entries(Pe))i.addOption(s,o);return i.setValue(this.plugin.settings.apiAuthType||"auto").onChange(async s=>{this.plugin.settings.apiAuthType=s,await this.plugin.saveSettings()})}),new g.Setting(n).setName(e("settingHeadersName")).setDesc(e("settingHeadersDesc")).addTextArea(i=>i.setPlaceholder("cf-aig-authorization: Bearer ...").setValue(this.plugin.settings.apiHeaders).onChange(async s=>{this.plugin.settings.apiHeaders=s,this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingMaxTokensName")).addText(i=>i.setValue(String(this.plugin.settings.apiMaxTokens)).onChange(async s=>{let o=parseInt(s,10);!Number.isNaN(o)&&o>0&&(this.plugin.settings.apiMaxTokens=o,this.plugin.saveSettingsDebounced())})),new g.Setting(n).setName(e("settingStreamingName")).setDesc(e("settingStreamingDesc")).addToggle(i=>i.setValue(this.plugin.settings.streaming??!0).onChange(async s=>{this.plugin.settings.streaming=s,await this.plugin.saveSettings()}))}new g.Setting(n).setName(e("settingModelName")).setDesc(e("settingModelDescApi")).addText(a=>a.setPlaceholder(w(this.plugin.settings).model||"model-id").setValue(this.plugin.settings.model).onChange(async i=>{this.plugin.settings.model=i.trim(),this.plugin.saveSettingsDebounced()})),new g.Setting(n).setName(e("settingMaxInputName")).setDesc(e("settingMaxInputDesc")).addText(a=>a.setValue(String(this.plugin.settings.maxDocChars||y.maxDocChars)).onChange(async i=>{let s=parseInt(i,10);!Number.isNaN(s)&&s>=1e3&&(this.plugin.settings.maxDocChars=s,this.plugin.saveSettingsDebounced())})),n.createEl("h3",{text:e("promptHeader")}),new g.Setting(n).setName(e("settingPromptLanguageName")).setDesc(e("settingPromptLanguageDesc")).addDropdown(a=>{for(let[i,s]of Object.entries(K))a.addOption(i,s);return a.setValue(this.plugin.settings.promptLanguage||y.promptLanguage).onChange(async i=>{this.plugin.settings.promptLanguage=i,await this.plugin.saveSettings()})}),new g.Setting(n).setName(e("settingCardRangeName")).setDesc(e("settingCardRangeDesc")).addText(a=>a.setPlaceholder("min").setValue(String(this.plugin.settings.minCards||y.minCards)).onChange(async i=>{let s=parseInt(i,10);!Number.isNaN(s)&&s>0&&(this.plugin.settings.minCards=s,this.plugin.settings.maxCardsa.setPlaceholder("max").setValue(String(this.plugin.settings.maxCards||y.maxCards)).onChange(async i=>{let s=parseInt(i,10);!Number.isNaN(s)&&s>0&&(this.plugin.settings.maxCards=Math.max(s,this.plugin.settings.minCards||y.minCards),this.plugin.saveSettingsDebounced())})),new g.Setting(n).setName(e("settingCustomPromptName")).setDesc(e("settingCustomPromptDesc")).addTextArea(a=>(a.inputEl.rows=8,a.setPlaceholder(e("settingCustomPromptPlaceholder")).setValue(this.plugin.settings.customSystemPrompt||"").onChange(async i=>{this.plugin.settings.customSystemPrompt=i,this.plugin.saveSettingsDebounced()}))),new g.Setting(n).setName(e("settingTestBackendName")).setDesc(e("settingTestBackendDescApi")).addButton(a=>a.setButtonText("Test").onClick(async()=>{try{let i=await Bt(this.plugin.settings);new g.Notice(`\u2713 ${i.slice(0,180)}`,8e3)}catch(i){new g.Notice(e("backendTestFailed",{error:i.message}),1e4)}})),new g.Setting(n).setName(e("settingExportFolderName")).setDesc(e("settingExportFolderDesc")).addText(a=>a.setValue(this.plugin.settings.exportFolder).onChange(async i=>{this.plugin.settings.exportFolder=i.trim()||y.exportFolder,this.plugin.saveSettingsDebounced()})),n.createEl("h3",{text:e("cacheHeader")}),new g.Setting(n).setName(e("settingMaxCacheName")).setDesc(e("settingMaxCacheDesc")).addText(a=>{a.setValue(String(this.plugin.settings.maxCacheEntries||_));let i=async()=>{let s=parseInt(a.getValue(),10);if(Number.isFinite(s)&&s>0){this.plugin.settings.maxCacheEntries=s,await this.plugin.saveSettings();let o=await this.plugin.pruneCacheIfNeeded();o.length>0&&new g.Notice(e("cachePruned",{count:o.length})),this.display()}};return a.inputEl.addEventListener("change",i),a.inputEl.addEventListener("keydown",s=>{s.key==="Enter"&&a.inputEl.blur()}),a});let r=Object.keys(this.plugin.cache).length;new g.Setting(n).setName(e("cachedNotesName",{count:r})).setDesc(e("cachedNotesDesc")).addButton(a=>a.setButtonText(e("clearAllCacheButton")).setWarning().onClick(async()=>{let i=Object.keys(this.plugin.cache).length;await this.plugin.cacheClear(),new g.Notice(e("cacheClearedAll",{count:i})),this.display()}))}};var S=require("obsidian");function N(t,n,e,r){let a=t.createEl("button",{cls:"parallel-reader-icon-button",attr:{type:"button","aria-label":e}});return a.title=e,typeof S.setIcon=="function"?(0,S.setIcon)(a,n):a.textContent=e,a.addEventListener("click",async i=>{i.preventDefault(),i.stopPropagation();try{await r()}catch(s){console.error(s),new S.Notice(`${e} failed: `+(s.message||s))}}),a}function D(t,n,e,r,a){let i=t.createEl("button",{cls:a||"parallel-reader-text-button",attr:{type:"button"}});return n&&typeof S.setIcon=="function"&&(0,S.setIcon)(i,n),i.createSpan({text:e}),i.addEventListener("click",async s=>{s.preventDefault(),s.stopPropagation();try{await r()}catch(o){console.error(o),new S.Notice(`${e} failed: `+(o.message||o))}}),i}async function I(t,n){try{await navigator.clipboard.writeText(t),new S.Notice(n)}catch(e){new S.Notice("Copy failed: "+(e.message||e))}}function _e(t){return String(t||"").split("/").map(n=>n.trim()).filter(n=>!!n&&n!==".."&&n!==".").join("/")}function Ve(t){let n=_e(t);if(!n)return[];let e=n.split("/");return e.map((r,a)=>e.slice(0,a+1).join("/"))}async function at(t,n){for(let e of Ve(n))if(!t.vault.getAbstractFileByPath(e))try{await t.vault.createFolder(e)}catch(r){if(!t.vault.getAbstractFileByPath(e))throw r}}var v=require("obsidian");var it=require("obsidian");var de=class extends it.Modal{constructor(n,e,r,a){super(n),this.plugin=e,this.card=r||{},this.onSave=a}onOpen(){let{contentEl:n}=this;n.empty(),n.createEl("h2",{text:this.plugin.t("editCardTitle")});let e=this.createLabeledInput(n,this.plugin.t("editCardTitleField"),this.card.title||""),r=this.createLabeledTextarea(n,this.plugin.t("editCardGistField"),this.card.gist||"",3),a=this.createLabeledTextarea(n,this.plugin.t("editCardBulletsField"),(this.card.bullets||[]).join(` +`),8),i=n.createDiv({cls:"parallel-reader-modal-actions"});D(i,null,this.plugin.t("editCardCancel"),()=>this.close(),"parallel-reader-text-button"),D(i,null,this.plugin.t("editCardSave"),async()=>{await this.onSave({title:e.value.trim()||this.card.title||"",gist:r.value.trim(),bullets:a.value.split(/\r?\n/).map(s=>s.trim()).filter(Boolean)}),this.close()},"parallel-reader-text-button")}createLabeledInput(n,e,r){let a=n.createDiv({cls:"parallel-reader-modal-field"});a.createEl("label",{text:e});let i=a.createEl("input",{attr:{type:"text"}});return i.value=r,i}createLabeledTextarea(n,e,r,a){let i=n.createDiv({cls:"parallel-reader-modal-field"});i.createEl("label",{text:e});let s=i.createEl("textarea");return s.rows=a,s.value=r,s}};var R="parallel-reader-view",he=class extends v.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 R}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)}async loadFor(e,r,a){this.sourceFile=e,this.sections=r,this.stale=!!a,this.loadingMessage="",this.errorMessage="",this.render()}async 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 a=this.containerEl.children[1],i=a.querySelector(".parallel-reader-streaming-preview");if(i){let h=i.querySelector("pre");h&&(h.textContent=r.slice(-2e3));let C=i.querySelector(".parallel-reader-stream-counter");C&&(C.textContent=`${r.length} chars`);return}a.empty();let o=a.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"});N(l,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(e)});let p=a.createDiv({cls:"parallel-reader-state parallel-reader-loading parallel-reader-streaming-preview"});p.createDiv({cls:"parallel-reader-spinner"});let c=p.createEl("div",{cls:"parallel-reader-state-title"});c.createSpan({text:this.plugin.t("loadingGenerating")+" "}),c.createSpan({cls:"parallel-reader-stream-counter",text:`${r.length} chars`});let u=p.createEl("pre",{cls:"parallel-reader-stream-text"});u.textContent=r.slice(-2e3)}async 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 a=r.createDiv({cls:"parallel-reader-empty"});a.createEl("h3",{text:e.basename}),a.createEl("p",{text:this.plugin.t("emptyNoCache")}),a.createEl("code",{text:this.plugin.t("commandGenerate")})}render(){let e=this.containerEl.children[1];e.empty();let a=e.createDiv({cls:"parallel-reader-header"}).createDiv({cls:"parallel-reader-header-row"});a.createEl("div",{text:this.sourceFile?.basename||"",cls:"parallel-reader-title"});let i=a.createDiv({cls:"parallel-reader-actions"});if(this.sourceFile&&(this.plugin.isGeneratingFile(this.sourceFile)?N(i,"square",this.plugin.t("actionCancel"),()=>{this.plugin.cancelGenerationForFile(this.sourceFile)}):N(i,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0)),N(i,"copy",this.plugin.t("actionCopyAll"),()=>this.plugin.copyCurrentViewMarkdown()),N(i,"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")}),D(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"}),D(l,"refresh-cw",this.plugin.t("actionRegenerate"),()=>this.plugin.runForFile(this.sourceFile,!0),"parallel-reader-text-button");return}let s=e.createDiv({cls:"parallel-reader-cards"});this.cards=[];let o=this.sourceFile?.path||"";this.sections.forEach((l,p)=>{let c=s.createDiv({cls:"parallel-reader-card"});c.dataset.idx=String(p),l.startLine<0&&c.addClass("parallel-reader-card-unanchored");let u=c.createEl("div",{cls:"parallel-reader-card-title"});if(u.createSpan({text:l.title}),l.startLine<0&&u.createEl("span",{text:" \u26A0",cls:"parallel-reader-warn",title:this.plugin.t("anchorMismatch")}),l.gist){let C=c.createEl("div",{cls:"parallel-reader-gist"});v.MarkdownRenderer.render(this.app,l.gist,C,o,this).catch(()=>{C.setText(l.gist)})}let h=l.bullets||[];if(h.length>0){let C=c.createEl("div",{cls:"parallel-reader-bullets-md"}),m=h.map(f=>`- ${f}`).join(` +`);v.MarkdownRenderer.render(this.app,m,C,o,this).catch(()=>{C.setText(m)})}else l.gist||c.createEl("div",{cls:"parallel-reader-empty-li",text:this.plugin.t("emptyCard")});c.addEventListener("click",C=>{let m=window.getSelection();if(m&&m.toString().length>0)return;let f=C.target;f&&f.tagName==="A"||l.startLine>=0&&this.plugin.scrollEditorToLine(l.startLine,this.sourceFile)}),c.addEventListener("contextmenu",C=>{C.preventDefault();let m=new v.Menu;m.addItem(f=>f.setTitle(this.plugin.t("menuCopyMarkdown")).setIcon("copy").onClick(()=>I(we(l),this.plugin.t("copiedMarkdown")))),m.addItem(f=>f.setTitle(this.plugin.t("menuCopyPlain")).setIcon("clipboard-copy").onClick(()=>I(qe(l),this.plugin.t("copiedPlain")))),l.anchor&&m.addItem(f=>f.setTitle(this.plugin.t("menuCopyAnchor")).setIcon("quote-glyph").onClick(()=>I(l.anchor,this.plugin.t("copiedAnchor")))),m.addSeparator(),l.startLine>=0&&m.addItem(f=>f.setTitle(this.plugin.t("menuJumpSource")).setIcon("arrow-right").onClick(()=>this.plugin.scrollEditorToLine(l.startLine,this.sourceFile))),m.addSeparator(),m.addItem(f=>f.setTitle(this.plugin.t("menuEditCard")).setIcon("pencil").onClick(()=>this.openEditCardModal(p))),m.addItem(f=>f.setTitle(this.plugin.t("menuDeleteCard")).setIcon("trash").onClick(()=>this.deleteCard(p))),m.showAtMouseEvent(C)}),this.cards.push(c)}),this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].addClass("is-active")}setActiveSection(e){e!==this.activeIdx&&(this.activeIdx>=0&&this.cards[this.activeIdx]&&this.cards[this.activeIdx].removeClass("is-active"),this.activeIdx=e,e>=0&&this.cards[e]&&(this.cards[e].addClass("is-active"),this.cards[e].scrollIntoView({block:"nearest",behavior:"smooth"})))}moveActiveSection(e){let r=ne(this.activeIdx,this.sections.length,e);return this.setActiveSection(r),this.focusSummaryPane(),r}jumpToActiveSection(){let e=ae(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=Z(this.sections,e);if(r.length===this.sections.length)return!1;let a=this.sections.length;return this.sections=r,this.activeIdx=Q(e,a,this.activeIdx),await this.plugin.cacheReplaceCards(this.sourceFile.path,r),this.render(),new v.Notice(this.plugin.t("cardDeleted")),!0}openEditCardModal(e){return!this.sourceFile||!this.sections[e]?!1:(new de(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 a=ee(this.sections,e,r);return a.length!==this.sections.length?!1:(this.sections=a,await this.plugin.cacheReplaceCards(this.sourceFile.path,a),this.render(),new v.Notice(this.plugin.t("cardSaved")),!0)}async exportToVault(){if(!this.sourceFile)return;let e=_e(this.plugin.settings.exportFolder),r=`${this.sourceFile.basename} - ${this.plugin.t("displayName")}.md`,a=`${e}/${r}`,i=["---",`source: [[${this.sourceFile.basename}]]`,`generated: ${new Date().toISOString().slice(0,10)}`,"tool: parallel-reader","---","",H(`${this.sourceFile.basename} \xB7 ${this.plugin.t("displayName")}`,this.sections),""].join(` +`),s=this.plugin.app;await at(s,e);let o=s.vault.getAbstractFileByPath(a);o instanceof v.TFile?await s.vault.modify(o,i):await s.vault.create(a,i),new v.Notice(this.plugin.t("exported",{path:a}))}};async function Ut(t,n,e,r){let{system:a,user:i}=ke(t,n),s,o=Ie(n),l=o?new AbortController:null;l&&e.onCancel(()=>l.abort()),o?s=await rt(a,i,n,r,l.signal):s=await ue(d.requestUrl,a,i,n);let p=s.map(c=>({title:c.title,level:2,anchor:c.anchor,gist:c.gist,startLine:X(t,c.anchor),bullets:c.bullets}));return p.sort((c,u)=>c.startLine<0&&u.startLine<0?0:c.startLine<0?1:u.startLine<0?-1:c.startLine-u.startLine),p}function st(t,n){return n?.phase==="generating"&&t&&Se(t.backend)?"cancelRequestedApiInFlight":"cancelRequested"}var Be=class extends d.Plugin{constructor(){super(...arguments);this._scrollDispose=null;this._settingsSaveTimer=null;this._cacheSaveTimer=null;this._cacheDirty=!1}t(e,r){return b(this.settings||y,e,r)}async onload(){await this.loadSettings(),this.jobs=new U,this._cacheSaveTimer=null,this._cacheDirty=!1,this.addRibbonIcon("book-open",this.t("ribbonOpen"),async()=>{let e=this.getActiveView();await this.ensureView(),e?.file&&await this.syncViewToFile(e.file)}),this.registerView(R,e=>new he(e,this)),this.addCommand({id:"parallel-reader-run",name:this.t("cmdRun"),callback:()=>this.runForActiveFile(!1)}),this.addCommand({id:"parallel-reader-regen",name:this.t("cmdRegen"),callback:()=>this.runForActiveFile(!0)}),this.addCommand({id:"parallel-reader-open-view",name:this.t("cmdOpenView"),callback:async()=>{let e=this.getActiveView();await this.ensureView(),e?.file&&this.syncViewToFile(e.file)}}),this.addCommand({id:"parallel-reader-export-current",name:this.t("cmdExport"),callback:()=>this.exportCurrentView()}),this.addCommand({id:"parallel-reader-copy-current-markdown",name:this.t("cmdCopyMarkdown"),callback:()=>this.copyCurrentViewMarkdown()}),this.addCommand({id:"parallel-reader-cancel-current",name:this.t("cmdCancel"),callback:()=>this.cancelActiveGeneration()}),this.addCommand({id:"parallel-reader-clear-current",name:this.t("cmdClearCurrent"),callback:async()=>{let e=this.getActiveView();if(!e?.file)return new d.Notice(this.t("noCurrentNote"));await this.cacheDelete(e.file.path),new d.Notice(this.t("cacheClearedFile",{name:e.file.basename}))}}),this.addCommand({id:"parallel-reader-clear-all",name:this.t("cmdClearAll"),callback:async()=>{let e=Object.keys(this.cache).length;await this.cacheClear(),new d.Notice(this.t("cacheClearedAll",{count:e}))}}),this.addCommand({id:"parallel-reader-card-prev",name:this.t("cmdCardPrev"),hotkeys:[{modifiers:["Alt"],key:"ArrowUp"}],callback:()=>this.moveActiveCard(-1)}),this.addCommand({id:"parallel-reader-card-next",name:this.t("cmdCardNext"),hotkeys:[{modifiers:["Alt"],key:"ArrowDown"}],callback:()=>this.moveActiveCard(1)}),this.addCommand({id:"parallel-reader-card-jump",name:this.t("cmdCardJump"),callback:()=>this.jumpActiveCard()}),this.addSettingTab(new pe(this.app,this)),this.registerEvent(this.app.workspace.on("active-leaf-change",()=>this.bindScrollSync())),this.registerEvent(this.app.workspace.on("file-open",e=>{e&&this.syncViewToFile(e)})),this.registerEvent(this.app.workspace.on("file-menu",(e,r)=>this.addFileMenuItems(e,r))),this.registerEvent(this.app.vault.on("rename",(e,r)=>this.handleFileRename(e,r))),this.registerEvent(this.app.vault.on("delete",e=>this.handleFileDelete(e))),this.bindScrollSync()}async onunload(){await this.flushSettingsSave(),await this.flushCacheSave(),this.app.workspace.detachLeavesOfType(R)}async loadSettings(){let r=(await this.loadData()||{}).settings||{};this.settings=Ee(Object.assign({},y,r)),await this.loadCache()}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())}async saveCache(){this._cacheSaveTimer&&(clearTimeout(this._cacheSaveTimer),this._cacheSaveTimer=null),this.pruneCache(),await this.writeCacheFile(),this._cacheDirty=!1}scheduleCacheSave(e=5e3){this._cacheDirty=!0,!this._cacheSaveTimer&&(this._cacheSaveTimer=setTimeout(()=>{this._cacheSaveTimer=null,this._cacheDirty&&this.saveCache().catch(r=>console.error("[parallel-reader] failed to save cache",r))},e))}async flushCacheSave(){this._cacheSaveTimer&&(clearTimeout(this._cacheSaveTimer),this._cacheSaveTimer=null),this._cacheDirty&&await this.saveCache()}cacheFilePath(){let e=this.app.vault.configDir||".obsidian",r=this.manifest?.id||"parallel-reader";return`${e}/plugins/${r}/cache.json`}async ensurePluginDataDir(){let e=this.app.vault.adapter,r=this.app.vault.configDir||".obsidian",a=this.manifest?.id||"parallel-reader",i=`${r}/plugins/${a}`;try{if(typeof e.exists=="function"&&await e.exists(i))return;await e.mkdir(i)}catch{}}async readCacheFile(){let e=this.app.vault.adapter;try{let r=await e.read(this.cacheFilePath()),a=JSON.parse(r);if(a&&typeof a=="object"&&a.entries&&typeof a.entries=="object")return a.entries}catch(r){let a=String(r?.message||r||"");/not found|does not exist|ENOENT/i.test(a)||console.warn("[parallel-reader] failed to read cache.json",r)}return{}}async writeCacheFile(){await this.ensurePluginDataDir(),await this.app.vault.adapter.write(this.cacheFilePath(),ge(this.cache))}async loadCache(){this.cache=await this.readCacheFile(),this.pruneCache().length>0&&await this.writeCacheFile()}pruneCache(){return Fe(this.cache,this.settings?.maxCacheEntries||_)}async pruneCacheIfNeeded(){let e=this.pruneCache();return e.length>0&&await this.saveCache(),e}cacheGet(e){return this.cache[e]||null}async cacheTouch(e){let r=me(this.cache[e]||null);return r?(this.scheduleCacheSave(),r):null}async cachePut(e,r,a,i){let s=new Date().toISOString();this.cache[e]={schemaVersion:J,contentHash:O(r),settingsHash:ie(i||this.settings),cards:a,generatedAt:s,lastAccessedAt:s},await this.saveCache()}async cacheReplaceCards(e,r){let a=this.cache[e];if(!a)return!1;let i=new Date().toISOString();return a.cards=(r||[]).map(s=>({title:s.title,anchor:s.anchor,gist:s.gist,bullets:s.bullets||[]})),a.updatedAt=i,a.lastAccessedAt=i,await this.saveCache(),!0}async cacheDelete(e){this.cache[e]&&(delete this.cache[e],await this.saveCache())}async cacheClear(){this.cache={},await this.saveCache()}async ensureView(){let{workspace:e}=this.app,r=e.getLeavesOfType(R)[0];return r||(r=e.getRightLeaf(!1),await r.setViewState({type:R,active:!0})),e.revealLeaf(r),r.view}getActiveView(){return this.app.workspace.getActiveViewOfType(d.MarkdownView)}getParallelView(){return this.app.workspace.getLeavesOfType(R)[0]?.view}moveActiveCard(e){let r=this.getParallelView();return r?.sections.length?r.moveActiveSection(e):(new d.Notice(this.t("noActiveCard")),-1)}jumpActiveCard(){let e=this.getParallelView();return!e||e.jumpToActiveSection()<0?(new d.Notice(this.t("noActiveCard")),!1):!0}isGeneratingFile(e){return!!e&&!!e.path&&this.jobs.isRunning(e.path)}cancelGenerationForFile(e){if(!e?.path)return new d.Notice(this.t("noCancelableJob")),!1;let r=this.jobs.get(e.path),a=st(this.settings,r),i=this.jobs.cancel(e.path);return new d.Notice(i?this.t(a):this.t("noCancelableJob")),i}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(){let e=this.getActiveView();if(e?.file&&this.cancelGenerationForFile(e.file))return;let r=this.getParallelView();r?.sourceFile?this.cancelGenerationForFile(r.sourceFile):new d.Notice(this.t("noCancelableJob"))}confirmRegenerateEditedCards(){let e=this.t("confirmRegenerateEditedCards");return typeof window<"u"&&typeof window.confirm=="function"?window.confirm(e):!0}addFileMenuItems(e,r){!(r instanceof d.TFile)||!r.path.endsWith(".md")||(e.addSeparator(),e.addItem(a=>a.setTitle(this.t("fileMenuGenerate")).setIcon("book-open").onClick(()=>this.runForFile(r,!1))),e.addItem(a=>a.setTitle(this.t("fileMenuRegen")).setIcon("refresh-cw").onClick(()=>this.runForFile(r,!0))),this.cacheGet(r.path)&&e.addItem(a=>a.setTitle(this.t("fileMenuClear")).setIcon("trash").onClick(async()=>{await this.cacheDelete(r.path),new d.Notice(this.t("cacheClearedFile",{name:r.basename}))})))}async handleFileRename(e,r){if(!(e instanceof d.TFile)||!r)return;let a=r.endsWith(".md"),i=e.path.endsWith(".md");if(!a&&!i)return;if(a&&!i){this.cache[r]&&(delete this.cache[r],await this.saveCache());let o=this.getParallelView();o&&o.sourceFile?.path===r&&o.renderEmpty();return}if(!a)return;this.cache[r]&&(this.cache[e.path]=this.cache[r],delete this.cache[r],await this.saveCache());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 d.TFile))return;this.cache[e.path]&&(delete this.cache[e.path],await this.saveCache());let r=this.getParallelView();r?.sourceFile?.path===e.path&&r.renderEmpty()}async exportCurrentView(){let e=this.getActiveView(),r=await this.ensureView();if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new d.Notice(this.t("noExportContent"));return}await r.exportToVault()}async copyCurrentViewMarkdown(){let e=this.getActiveView(),r=await this.ensureView();if((!r.sourceFile||!r.sections.length)&&e?.file&&await this.syncViewToFile(e.file),!r.sourceFile||!r.sections.length){new d.Notice(this.t("noCopyContent"));return}await I(H(`${r.sourceFile.basename} \xB7 ${this.t("displayName")}`,r.sections),this.t("copiedAllMarkdown"))}async runForActiveFile(e){let r=this.getActiveView();if(!r?.file){new d.Notice(this.t("openNoteFirst"));return}return this.runForFile(r.file,e)}async runForFile(e,r){if(!e){new d.Notice(this.t("openNoteFirst"));return}if(this.jobs.isRunning(e.path)){new d.Notice(this.t("alreadyGenerating"));return}if(fe(this.cacheGet(e.path),r)&&!this.confirmRegenerateEditedCards()){new d.Notice(this.t("regenerateCancelled"));return}let a=null;return this.jobs.start(e.path,async i=>{i.setPhase("reading");let s=await this.app.vault.read(e);if(i.throwIfCancelled(),!s.trim()){new d.Notice(this.t("emptyNote"));return}if(a=await this.ensureView(),i.throwIfCancelled(),i.setPhase("cache-check"),!r){let h=this.cacheGet(e.path);if(se(h,s,this.settings)){await this.cacheTouch(e.path),this.activeFileStillMatches(e)&&await a.loadFor(e,this.resolveCardAnchors(s,h.cards),!1);return}}await a.renderLoading(e,this.t("loadingGenerating"));let o=Number(this.settings.maxDocChars)||y.maxDocChars;s.length>o&&new d.Notice(this.t("longNoteTruncated",{count:o})),new d.Notice(this.t("generatingNotice")),i.setPhase("generating");let l=a?h=>{!h.done&&this.viewIsShowingFile(a,e)&&a.renderStreamingPreview(e,h.accumulated)}:void 0,p=await Ut(s,this.settings,i,l);if(i.throwIfCancelled(),p.length===0){new d.Notice(this.t("noCardsReturned"));return}let c=p.map(h=>({title:h.title,anchor:h.anchor,gist:h.gist,bullets:h.bullets}));i.setPhase("saving"),await this.cachePut(e.path,s,c,this.settings),i.throwIfCancelled(),this.viewIsShowingFile(a,e)&&await a.loadFor(e,p,!1);let u=p.filter(h=>h.startLine<0).length;new d.Notice(this.t("generationDone",{count:p.length,suffix:u?this.t("unanchoredSuffix",{count:u}):""}))}).catch(async i=>{if(i instanceof L){new d.Notice(i.message);return}if(i instanceof P){a&&this.viewIsShowingFile(a,e)&&await a.renderError(e,this.t("cancelledError")),new d.Notice(this.t("cancelled"));return}let s=Ce(i);console.error(i),a&&this.viewIsShowingFile(a,e)&&await a.renderError(e,i.message||String(i)),new d.Notice(this.t("generationFailed",{kind:s==="unknown"?"":` (${s})`,error:i.message||i}))})}resolveCardAnchors(e,r){let a=(r||[]).map(i=>({title:i.title,level:2,anchor:i.anchor,gist:i.gist,startLine:X(e,i.anchor),bullets:i.bullets||[]}));return a.sort((i,s)=>i.startLine<0&&s.startLine<0?0:i.startLine<0?1:s.startLine<0?-1:i.startLine-s.startLine),a}async syncViewToFile(e){if(!e?.path?.endsWith(".md"))return;let r=this.app.workspace.getLeavesOfType(R);if(r.length===0)return;let a=r[0].view;if(!a||!this.activeFileStillMatches(e))return;let i=this.cacheGet(e.path);if(!i){await a.loadFor(e,[],!1),a.renderEmptyWithHint(e);return}let s=await this.app.vault.read(e);if(!this.activeFileStillMatches(e))return;let o=!se(i,s,this.settings);await this.cacheTouch(e.path),await a.loadFor(e,this.resolveCardAnchors(s,i.cards),o)}bindScrollSync(){this._scrollDispose&&(this._scrollDispose(),this._scrollDispose=null);let e=this.getActiveView();if(!e)return;let r=e.editor,a=r&&r.cm,i=a?.scrollDOM?a.scrollDOM:e.contentEl.querySelector(".cm-scroller");if(!i)return;let s=Oe(()=>this.handleEditorScroll(e));i.addEventListener("scroll",s,{passive:!0}),this._scrollDispose=()=>{s.cancel(),i.removeEventListener("scroll",s)}}handleEditorScroll(e){let r=this.getParallelView();if(!r||!e.file||r.sourceFile?.path!==e.file.path)return;let a=e.editor,i=a&&a.cm;if(!i?.scrollDOM)return;let s=i.scrollDOM.getBoundingClientRect(),o=Le(s),l=0;try{let c=i.posAtCoords({x:s.left+20,y:o});c!=null&&(l=i.state.doc.lineAt(c).number-1)}catch{return}let p=-1;for(let c=0;c abortController.abort()); } - switch (settings.backend) { - case 'codex': - cards = await summarizeViaCodex(system, user, settings, job); - break; - case 'api': - case 'anthropic-api': - if (useStreaming) { - cards = await summarizeViaApiStreaming(system, user, settings, onStreamProgress, abortController!.signal); - } else { - cards = await summarizeViaApi(requestUrl, system, user, settings); - } - break; - default: - cards = await summarizeViaClaudeCode(system, user, settings, job); - break; + if (useStreaming) { + cards = await summarizeViaApiStreaming(system, user, settings, onStreamProgress, abortController!.signal); + } else { + cards = await summarizeViaApi(requestUrl, system, user, settings); } const resolved: ResolvedCard[] = cards.map((c) => ({ title: c.title, diff --git a/src/settings-tab.ts b/src/settings-tab.ts index dc7cc2c..e160f52 100644 --- a/src/settings-tab.ts +++ b/src/settings-tab.ts @@ -1,7 +1,6 @@ 'use strict'; import { type App, Notice, type Plugin, PluginSettingTab, requestUrl, Setting } from 'obsidian'; -import { resolveCliPath, runCli } from './cli'; import { testApiBackend } from './providers'; import { API_AUTH_TYPES, @@ -12,27 +11,13 @@ import { DEFAULT_SETTINGS, getApiFormat, getApiPreset, - isApiBackend, PROMPT_LANGUAGES, UI_LANGUAGES, } from './settings'; import type { PluginHost, PluginSettings } from './types'; async function testBackend(settings: PluginSettings) { - if (settings.backend === 'codex') { - const cmd = resolveCliPath('codex', settings.cliPath); - const { stdout } = await runCli(cmd, ['--version'], '', 10000); - return `codex @ ${cmd}\n${stdout.trim()}`; - } - if (settings.backend === 'claude-code') { - const cmd = resolveCliPath('claude', settings.cliPath); - const { stdout } = await runCli(cmd, ['--version'], '', 10000); - return `claude @ ${cmd}\n${stdout.trim()}`; - } - if (isApiBackend(settings.backend)) { - return testApiBackend(requestUrl, settings); - } - throw new Error('Unknown backend: ' + settings.backend); + return testApiBackend(requestUrl, settings); } export class ParallelReaderSettingTab extends PluginSettingTab { @@ -63,54 +48,9 @@ export class ParallelReaderSettingTab extends PluginSettingTab { }); }); - new Setting(containerEl) - .setName(tr('settingBackendName')) - .setDesc(tr('settingBackendDesc')) - .addDropdown((d) => - d - .addOption('claude-code', 'Claude Code CLI') - .addOption('codex', 'Codex CLI') - .addOption('api', 'API / Provider') - .addOption('anthropic-api', 'Anthropic API (legacy)') - .setValue(this.plugin.settings.backend) - .onChange(async (v) => { - this.plugin.settings.backend = v; - if (v === 'api' && !this.plugin.settings.apiBaseUrl) { - applyApiProviderPreset(this.plugin.settings, this.plugin.settings.apiProvider || 'anthropic'); - } - await this.plugin.saveSettings(); - this.display(); - }), - ); - - const apiBackend = isApiBackend(this.plugin.settings.backend); - - if (!apiBackend) { - new Setting(containerEl) - .setName(tr('settingCliPathName')) - .setDesc(tr('settingCliPathDesc')) - .addText((t) => - t - .setPlaceholder(tr('settingCliPathPlaceholder')) - .setValue(this.plugin.settings.cliPath) - .onChange(async (v) => { - this.plugin.settings.cliPath = v.trim(); - this.plugin.saveSettingsDebounced(); - }), - ); - - new Setting(containerEl).setName(tr('settingCliTimeoutName')).addText((t) => - t.setValue(String(this.plugin.settings.cliTimeoutMs)).onChange(async (v) => { - const n = parseInt(v, 10); - if (!Number.isNaN(n) && n > 0) { - this.plugin.settings.cliTimeoutMs = n; - this.plugin.saveSettingsDebounced(); - } - }), - ); - } else { - containerEl.createEl('h3', { text: tr('apiProviderHeader') }); + containerEl.createEl('h3', { text: tr('apiProviderHeader') }); + { const preset = getApiPreset(this.plugin.settings); new Setting(containerEl) .setName(tr('settingProviderPresetName')) @@ -233,13 +173,13 @@ export class ParallelReaderSettingTab extends PluginSettingTab { new Setting(containerEl) .setName(tr('settingModelName')) - .setDesc(apiBackend ? tr('settingModelDescApi') : tr('settingModelDescCli')) + .setDesc(tr('settingModelDescApi')) .addText((t) => t - .setPlaceholder(apiBackend ? getApiPreset(this.plugin.settings).model || 'model-id' : DEFAULT_SETTINGS.model) + .setPlaceholder(getApiPreset(this.plugin.settings).model || 'model-id') .setValue(this.plugin.settings.model) .onChange(async (v) => { - this.plugin.settings.model = v.trim() || (apiBackend ? '' : DEFAULT_SETTINGS.model); + this.plugin.settings.model = v.trim(); this.plugin.saveSettingsDebounced(); }), ); @@ -319,7 +259,7 @@ export class ParallelReaderSettingTab extends PluginSettingTab { new Setting(containerEl) .setName(tr('settingTestBackendName')) - .setDesc(apiBackend ? tr('settingTestBackendDescApi') : tr('settingTestBackendDescCli')) + .setDesc(tr('settingTestBackendDescApi')) .addButton((b) => b.setButtonText('Test').onClick(async () => { try { diff --git a/src/settings.ts b/src/settings.ts index 04694da..00dd453 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -21,7 +21,7 @@ export const UI_LANGUAGES = { export const DEFAULT_SETTINGS: PluginSettings = { uiLanguage: 'auto', - backend: 'claude-code', + backend: 'api', cliPath: '', apiProvider: 'anthropic', apiFormat: 'anthropic-messages',