Add an optional ownership-scoped Tectonic environment with secure installation, native compile verification, and topology-preserving repair support. Harden CircuitikZ generation and preview routing, fix the history drawer scroll model, and synchronize bilingual architecture, user guidance, and localized README documentation.
18 KiB
Notemd 系统架构总览
更新:2026-07-10
系统架构
flowchart TB
subgraph User["Obsidian 用户界面"]
CMD["命令面板"]
SIDEBAR["Notemd 工作台"]
SETTINGS["设置标签页"]
end
subgraph Plugin["NotemdPlugin (src/main.ts)"]
LOAD["loadSettings / saveSettings"]
DISPATCH["命令分发"]
BATCH["批量处理"]
end
subgraph LLM["LLM 调用管道"]
PROV["提供商注册<br/>(src/llmProviders.ts)"]
TOKEN["令牌解析<br/>(resolveProviderTokenLimit)"]
CACHE["响应缓存<br/>(llmResponseCache)"]
TRANS["传输层<br/>5 个运行时"]
end
subgraph Diagram["图表平台"]
PROMPT["规格提示<br/>(diagramSpecPrompt)"]
GEN["生成服务<br/>(generateDiagramArtifact)"]
PARSE["规格解析<br/>(parseDiagramSpecResponse)"]
RENDER["渲染服务<br/>(RendererRegistry)"]
HOST["预览宿主<br/>(IframeRenderHost)"]
end
subgraph Output["输出"]
VAULT["Vault 文件<br/>(.md, .canvas, .json)"]
PREVIEW["图表预览弹窗"]
EXPORT["源文件 / SVG / PNG / PDF 导出"]
end
CMD --> DISPATCH
SIDEBAR --> DISPATCH
SETTINGS --> LOAD
DISPATCH --> PROV
PROV --> TOKEN
TOKEN --> CACHE
CACHE --> TRANS
TRANS --> GEN
TRANS --> BATCH
GEN --> PROMPT
PROMPT --> PARSE
PARSE --> RENDER
RENDER --> HOST
HOST --> PREVIEW
HOST --> EXPORT
RENDER --> VAULT
BATCH --> VAULT
LLM 调用管道
sequenceDiagram
participant User as 用户
participant Plugin as NotemdPlugin
participant Provider as 提供商注册
participant Token as 令牌解析
participant Cache as 响应缓存
participant Transport as 传输层
participant API as LLM API
User->>Plugin: 执行操作(处理、翻译、生成)
Plugin->>Provider: getLLMProviderDefinition(name)
Provider-->>Plugin: LLMProviderDefinition(传输协议、API 密钥模式等)
Plugin->>Token: resolveProviderTokenLimit(provider, model, maxTokens)
Token->>Token: KNOWN_MODEL_MAX_OUTPUT_TOKENS 查表
Token-->>Plugin: 令牌上限 (number | undefined)
Plugin->>Cache: buildCacheKey(provider, model, prompt, content)
Plugin->>Cache: getCachedResponse(cacheKey)
alt 缓存命中
Cache-->>Plugin: 缓存响应
Plugin-->>User: 结果
else 缓存未命中
Plugin->>Transport: callLLM(provider, prompt, content, settings)
Note over Transport: 分发至 5 个运行时之一<br/>openai-compatible | anthropic | google<br/>azure-openai | ollama
Transport->>API: HTTP 请求(含重试逻辑)
API-->>Transport: 响应
Transport-->>Plugin: 结果
Plugin->>Cache: setCachedResponse(cacheKey, result)
Plugin-->>User: 结果
end
令牌解析逻辑
用户配置 (maxTokens, provider.maxOutputTokens)
→ resolveProviderTokenLimit()
→ 连接测试? → 返回 1
→ 提供商 maxOutputTokens 覆盖已设置?
→ 已知模型? → min(覆盖值, 已知上限)
→ 未知模型? → 覆盖值(直接使用)
→ 全局 maxTokens 已设置?
→ 已知模型?
→ maxTokens === DEFAULT? → 已知模型上限(自动)
→ 否则 → min(maxTokens, 已知上限)
→ 未知模型?
→ maxTokens === DEFAULT? → undefined(API 自行决定,Cline 对齐)
→ 否则 → maxTokens(用户值)
→ 否则 → 已知上限 ?? undefined
支持的传输协议
| 传输协议 | 提供商数量 | 协议 |
|---|---|---|
openai-compatible |
22 个提供商 | OpenAI Chat Completions API |
anthropic |
1 个 | Anthropic Messages API |
google |
1 个 | Google Gemini API |
azure-openai |
1 个 | Azure OpenAI Deployment API |
ollama |
1 个 | Ollama Native API |
图表渲染平台
flowchart LR
subgraph Input["输入"]
MD["Markdown 内容"]
INTENT["首选意图<br/>(可选)"]
end
subgraph Spec["规格层"]
PLAN["DiagramPlan<br/>(意图推断)"]
PROMPT2["DiagramSpec 提示"]
LLM["LLM 调用"]
PARSE2["规格解析器"]
VALIDATE["规格验证器"]
end
subgraph Render["渲染层"]
REGISTRY["RendererRegistry<br/>8 个渲染器"]
SERVICE["RendererService"]
CACHE2["RenderCache"]
end
subgraph Target["输出目标"]
MERMAID["Mermaid<br/>(流程图、时序、类、ER、状态、思维导图)"]
CANVAS["JSON Canvas<br/>(画布图)"]
VEGA["Vega-Lite<br/>(数据图表)"]
HTML["HTML 回退"]
FIGURE["可编辑 HTML/SVG"]
BOARD["Draw.io / Drawnix"]
CIRCUIT["Circuitikz"]
end
subgraph Host["预览层"]
IFRAME["IframeRenderHost"]
MODAL["DiagramPreviewModal"]
EXPORT2["源文件 / SVG / PNG / PDF 导出"]
end
MD --> PLAN
INTENT --> PLAN
PLAN --> PROMPT2
PROMPT2 --> LLM
LLM --> PARSE2
PARSE2 --> VALIDATE
VALIDATE --> SERVICE
SERVICE --> REGISTRY
REGISTRY --> MERMAID
REGISTRY --> CANVAS
REGISTRY --> VEGA
REGISTRY --> HTML
REGISTRY --> FIGURE
REGISTRY --> BOARD
REGISTRY --> CIRCUIT
MERMAID --> IFRAME
CANVAS --> IFRAME
VEGA --> IFRAME
IFRAME --> MODAL
MODAL --> EXPORT2
支持的图表意图
| 意图 | 渲染目标 | 渲染器 | 预览 | 导出 |
|---|---|---|---|---|
mindmap |
mermaid | MermaidRenderer | 弹窗/iframe | SVG、PNG |
flowchart |
mermaid | MermaidRenderer | 弹窗/iframe | SVG、PNG |
sequence |
mermaid | MermaidRenderer | 弹窗/iframe | SVG、PNG |
classDiagram |
mermaid | MermaidRenderer | 弹窗/iframe | SVG、PNG |
erDiagram |
mermaid | MermaidRenderer | 弹窗/iframe | SVG、PNG |
stateDiagram |
mermaid | MermaidRenderer | 弹窗/iframe | SVG、PNG |
canvasMap |
json-canvas | JsonCanvasRenderer | 弹窗/iframe | 源文件、SVG、PNG、PDF |
dataChart |
vega-lite | VegaLiteRenderer | 弹窗/iframe(沙盒) | 源文件、SVG、PNG、PDF |
circuit |
circuitikz | CircuitikzRenderer | SVG companion 或 source-only 预览 | .tex、SVG、PNG、PDF |
显式渲染目标
对 Generate diagram 与 Preview diagram 而言,规格优先 pipeline 可以在意图推断之外显式指定渲染目标。标准的 Summarise as Mermaid diagram 命令仍保持 Mermaid 兼容输出。
| 渲染目标 | Artifact 边界 | Runtime 依赖策略 |
|---|---|---|
editable-html-svg |
带语义 inline SVG 的自包含 HTML | 不依赖外部编辑器 runtime |
drawio |
.drawio XML 加 SVG/MD review companion |
插件内不嵌入 diagrams.net runtime |
drawnix |
.drawnix JSON 子集加 SVG/MD review companion |
插件内不嵌入 Drawnix 或 Plait runtime |
circuitikz |
经过验证的 .tex 源文件加 SVG/MD review companion |
预览/导出零依赖;桌面端可选本机编译器或托管 Tectonic |
Circuitikz 支持仍然是受约束的。前端设置无需开启 Developer mode 就会显示 Circuit (Circuitikz) 首选图表类型与 Circuitikz + SVG preview 首选渲染目标,但 renderer 只接受经过验证的 DiagramSpec(intent: "circuit", circuitSpec)。它会写出确定性的 circuitikz TeX 和可审阅的 SVG companion。桌面用户随后可以复用自定义/系统编译器,或在 Vault 外显式安装固定版本 Tectonic 0.16.9,用于编译诊断、原生 PDF 证据与受保护的修复验收;移动端与常规预览/导出不会加载桌面进程代码。
托管运行时边界按所有权而不是目录名称判断。下载资产经过主机白名单、体积上限和 checksum 校验,解压拒绝链接与路径穿越,在 staging 中通过 smoke 后才在文件系统锁内激活。已有路径必须在规范化 realpath 解析后仍位于配置的运行时根目录内。删除只接受有效 Notemd pointer 或安装目录内所有权证据;过期锁恢复会先把已声明的死亡 owner 锁原子隔离,再复核 owner 与 claim token 后删除。
模块地图
| 模块 | 职责 |
|---|---|
src/main.ts |
插件入口、命令注册、流程编排 |
src/llmProviders.ts |
26 个提供商定义、元数据、KNOWN_MODEL 表 |
src/llmUtils.ts |
传输分发、令牌解析、重试、响应缓存 |
src/fileUtils.ts |
文件处理、Mermaid 修复、概念提取 |
src/searchUtils.ts |
网络搜索、Tavily/DuckDuckGo 集成 |
src/translate.ts |
翻译管道(含分块) |
src/promptUtils.ts |
任务提示词(旧版 + spec-first) |
src/diagram/ |
图表领域模型、适配器、渲染器 |
src/rendering/ |
渲染宿主、预览、导出、主题 |
src/ui/ |
设置标签页、侧边栏、弹窗、欢迎页 |
src/i18n/ |
22 种语言、任务语言策略 |
src/operations/ |
operation registry、host adapter、capability/contract 导出、可复用命令编排 |
src/batchProgressStore.ts |
中断恢复批量状态持久化 |
src/providerDiagnostics.ts |
LLM 提供商连接诊断 |
CLI 边界现实
当前宿主事实必须明确写清:
- 可选的
obsidian-cli包装器可能提供native等桌面/调试入口,但当前 Windows Study 主机并未安装;npm 上同名的旧包早于官方 CLI,且会遮蔽obsidian可执行文件,因此不能作为安全替代品 - 官方
obsidianCLI 已支持commands、command id=<command-id>与eval,能够列出/执行插件命令,也可直接调用 maintainer bridge scripts/invoke-maintainer-cli-operation.js在兼容包装器存在时优先使用obsidian-cli native eval,只在命令不存在时回退到官方obsidian eval;如果包装器已存在但执行失败,则原样暴露失败,不会静默掩盖- 但这仍然只是命令触发表面,不是成熟的插件集成协议:它还缺少类型化参数、返回结果契约、能力元数据和稳定自动化语义
因此,Notemd 的未来 CLI 路线仍不能停留在“把 sidebar 按钮搬到终端”。真正值得抽取的是已经开始具备独立形态的低层能力:
src/providerDiagnostics.tssrc/diagram/diagramGenerationService.tssrc/workflowButtons.tssrc/batchProgressStore.tsLLMProviderConfig.localOnly这类 config/profile 语义
当前架构缺口在于:src/main.ts 仍持有过多 orchestration、UI 生命周期和 Obsidian runtime 耦合。在形成宿主无关 operation 层之前,插件 command IDs 虽然已经可以被官方 CLI 触发,但它们仍然只是产品表面,不应被当成稳定工程 API。
不过这个缺口已经比之前更小了:
src/operations/diagramGenerateOperation.ts现在承接命令层之下可复用的 diagram 执行逻辑src/operations/providerDiagnosticCommand.ts现在承接命令层之下的 provider diagnostic command orchestrationsrc/operations/diagramCommandHostAdapter.ts现在承接 Mermaid/artifact 保存收尾、直接 Vega-Lite 预览编排,以及公共 diagram command wrapper(runGenerateDiagramCommandWithHost、runPreviewExperimentalDiagramCommandWithHost)src/operations/configProfileCommands.ts现在承接 provider profile 导入导出与 CLI capability/contract 导出编排src/operations/providerDiagnosticReportPersistence.ts现在承接带冲突规避的 provider diagnostic report 文件创建逻辑src/operations/providerDiagnosticCommandHostAdapter.ts现在承接开发者诊断命令的宿主装载、报告落盘接线与 notice 整形逻辑src/operations/configProfileCommandHostAdapter.ts现在承接 config/profile 状态持久化、CLI 导出 notice 整形与导入导出错误映射逻辑src/operations/providerConnectionTestCommandHostAdapter.ts现在承接共享 provider 连接测试的 settings 装载,以及底层测试 runner 与交互式 busy/reporter wrapper,并已被命令路径与设置页共同复用src/operations/noteProcessingCommandHostAdapter.ts现在不仅承接process-current-add-links、process-folder-add-links、batch-generate-from-titles、generate-from-title与research-and-summarize,还继续承接translate-current-file、batch-translate-folder、extract-concepts-current、extract-concepts-folder、extract-original-text与extract-concepts-and-generate-titles的 busy-guard、reporter 生命周期、notice/error-log 编排逻辑src/operations/utilityCommandHostAdapter.ts现在也已承接当前文件 duplicate check、duplicate cleanup、batch Mermaid fix 与 single/batch formula fix 的 command orchestration;check-for-duplicates已不再内联写在命令注册里src/operations/utilityCommandHostAdapter.ts现在也已承接 duplicate cleanup 与 batch Mermaid fix 的删除确认、无文件 notice 与成功 notice 语义,这些用户侧效果已不再从src/fileUtils.ts泄漏出来src/operations/registry.ts现在也已覆盖剩余 selection/export 邻接自动化表面:editor.create-link-and-generate、provider.profile.export、provider.profile.import、cli.capability-manifest.export与cli.invocation-contract.export已进入与前几批相同的 registry/capability/contract 表面- 第一批
src/fileUtils.ts子切片也已经完成 write-heavy contract enrichment 验证:processFile()现在返回ProcessFileResult,generateContentForTitle()返回GenerateContentForTitleResult,batchGenerateContentForTitles()返回BatchGenerateContentForTitlesResult,runProcessFolderWithNotemdCommandWithHost()现在也会返回带savedCount、fileResults、errors与cancelled的BatchProcessFolderResult src/fileUtils.ts现在不再自行决定“无可处理 Markdown 文件”的用户侧批量生成结果;它只返回结构化 batch state,这一 no-file notice 语义改由src/operations/noteProcessingCommandHostAdapter.ts承接src/fileUtils.ts的剩余尾部现在也已落地:batchFixMermaidSyntaxInFolder()返回BatchMermaidFixResult,checkAndRemoveDuplicateConceptNotes()返回ConceptDedupeResult,破坏性确认由 host adapter 注入,batch Mermaid 的无文件处理也已从 utility-owned 改为 host-ownedsrc/operations/registry.ts现在也直接建模了file.process-add-links、file.process-folder-add-links、content.generate-from-title、content.batch-generate-from-titles、mermaid.batch-fix、concept.dedupe、translate.*与formula.*的 richer result schema,因此 capability export 与 invocation-contract export 不再把这些流程压平成仅路径或仅计数语义src/fileUtils.ts与src/extractOriginalText.ts现在已经接受更窄的 runtime context,而不是直接依赖具体NotemdPlugin类,这说明边界正在从 wrapper 抽离继续推进到 utility 对宿主类型耦合的削弱src/main.ts现在主要保留命令注册、host 构造,以及更深一层的 diagram 执行 helper;先前最高价值的公共 direct command surface 现在已经改为通过 host adapter 代理,不再内联 busy/reporter/preview 生命周期逻辑- 新落地的 direct-surface wrapper 批次已经覆盖
testLlmConnectionCommand、generateDiagramCommand与previewExperimentalDiagramCommand;这些表面现在都具备结构化 result 边界,而不是 fire-and-forget 的 UI glue - 最新一层细化是:
diagram.generate应被理解为“宿主无关 generation contract”,而不是对当前 active-file 命令的另一种命名。它在 operation-level 上的safe/read-only元数据描述的是显式的sourceMarkdown -> DiagramGenerationResultcore;映射过去的 command binding 仍然要如实保留requires-active-file/write-file语义。 - 当前真正剩余的缺口因此已经不是公共 command entrypoint 本身:
diagram.preview与provider.connection.test现已具备 typed contract,save/artifact 的实质执行路径也已进入src/operations/diagramCommandExecution.ts,而diagram.generate现在也会返回显式的 follow-through 细节(kind、outputPath、previewOpened、autoFixAttempted、artifactTarget),同时继续保留向后兼容的顶层outputPath/previewOpened字段。 - 维护者本地语义核验层现在也不再只是文字说明:
npm run verify:diagram-semantics已能生成无 secrets 的 Markdown 检查模板,其中包含仓库硬门、vault 感知的 CLI 检查命令,以及 Mermaid / JSON Canvas / Vega-Lite 的证据区块,不依赖仓库中跟踪的 vault 路径或 live 凭据。 - 下一阶段顺序已经明确:先把
diagram.generate保持为宿主无关 core,把这批已落地的 typed follow-through 视作其下的 command-completion 层,再做 packaging / semantic verification 的后续收敛,最后才重开更强 public CLI 声明或更大规模的结构重排。
关键设计决策
- 规格优先图表生成:LLM 输出结构化
DiagramSpecJSON,而非原始 Mermaid 语法。解耦意图与渲染器。 - 传输驱动分发:OpenAI-compatible 提供商共享一个运行时。无逐提供商代码路径。
- Cline 对齐令牌解析:未知模型由 API 提供商自行决定。已知模型使用元数据表。
- operation-core 与 command-binding 分层:registry 中的 operation 元数据可以描述可复用的宿主无关 core,而当前出货命令本身仍保留 active-file、write-file 或 preview-bound 的真实产品语义。
diagram.generate是当前最明确的证明案例。 - Iframe 宿主预览:Vega-Lite 和 HTML 在沙盒 iframe 中渲染。Mermaid 内联渲染。
- 本地存储提供商配置:API 密钥可设备本地保留,工作流设置可同步。
- 响应缓存:5 分钟 TTL 内相同 LLM 调用返回缓存结果。
验证
npm run build— TypeScript 编译 + esbuild 打包npm test -- --runInBand— 完整 Jest 矩阵当前为 137 套件、871 项测试;若在/.worktrees/checkout 中验证,请改用npx jest --runInBand --config /tmp/notemd-worktree-jest.cjs,因为仓库默认 Jest ignore 规则会排除 worktree 路径npm run audit:i18n-ui— 无硬编码 UI 字符串npm run audit:render-host— 渲染宿主自包含于 main.jsgit diff --check— 空白符卫生