mirror of
https://github.com/yan-istart/IStart-Note-AI-Plugin.git
synced 2026-07-22 06:51:37 +00:00
feat(2.0.0): three-module architecture — knowledge/execution/auxiliary
M1: README正式化 + badges + 三模块叙事 + 状态修正 + 版本对齐2.0.0
M2: 设置页改为侧边导航(知识/执行/辅助) + 分区布局CSS
M3: ActionDomain(knowledge/execution/auxiliary) 替代旧ActionGroup + 面板三栏
M4: Scheduler MVP — ScheduledTaskRunner + NextRunCalculator + 知识债务扫描 + 百度备份任务
KnowledgeIndexService 新增 getAll() 公开方法
KnowledgeDebtModal 改用公开API
This commit is contained in:
parent
cb4bd88c65
commit
7b26626ccb
17 changed files with 1023 additions and 535 deletions
231
README.md
231
README.md
|
|
@ -1,125 +1,127 @@
|
|||
# IStart-Note-AI
|
||||
|
||||
> [!warning] Beta software
|
||||
> IStart-Note-AI is under active development. Features marked **experimental** below may change or be removed without notice. The data model and frontmatter schema are not yet stable; back up your vault before trying new features.
|
||||
<p align="center">
|
||||
<strong>Turn your Obsidian vault into a knowledge-to-action system.</strong>
|
||||
</p>
|
||||
|
||||
[简体中文 README →](./README.zh-CN.md)
|
||||
<p align="center">
|
||||
<a href="./README.zh-CN.md">简体中文</a> ·
|
||||
<a href="#quick-start">Quick Start</a> ·
|
||||
<a href="#privacy">Privacy</a> ·
|
||||
<a href="#roadmap">Roadmap</a>
|
||||
</p>
|
||||
|
||||
An Obsidian plugin that turns your notes into a structured personal knowledge system. One unified AI assistant helps you draft, expand, and organize notes, with automatic concept pages, bidirectional links, reading projects, and optional Baidu Cloud sync.
|
||||
|
||||
The plugin is built around an OpenAI-compatible chat completions API. The default provider is [DeepSeek](https://platform.deepseek.com); other compatible endpoints work by changing the Base URL.
|
||||
<p align="center">
|
||||
<img alt="Version" src="https://img.shields.io/github/v/release/yan-istart/IStart-Note-AI-Plugin?include_prereleases">
|
||||
<img alt="License" src="https://img.shields.io/github/license/yan-istart/IStart-Note-AI-Plugin">
|
||||
<img alt="CI" src="https://img.shields.io/github/actions/workflow/status/yan-istart/IStart-Note-AI-Plugin/ci.yml?branch=main">
|
||||
<img alt="Obsidian" src="https://img.shields.io/badge/Obsidian-1.7.2%2B-7C3AED">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Status overview
|
||||
IStart-Note-AI is an Obsidian plugin built around three modules — **Knowledge**, **Execution**, and **Auxiliary** — that help you turn scattered notes into a searchable, interlinked, and actionable personal knowledge system.
|
||||
|
||||
| Feature | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| AI Assistant (insert / replace / append / show) | stable | Single command panel entry, content classifier + structured prompt + markdown beautifier |
|
||||
| Reading projects | stable | Generate skeleton, chapter pre-reading questions, summaries, Feynman tests |
|
||||
| Baidu Cloud sync (notes + plugin config) | stable | Manual and auto modes, conflict strategy, plugin/Obsidian config backup |
|
||||
| Document beautification | stable | Restructures headings, adds callouts and Mermaid diagrams |
|
||||
| Concept pages auto-creation | stable | Empty pages created via `[[concept]]` link scan |
|
||||
| Concept page completion (`ConceptCompleter`) | experimental | Internals exist; not exposed in the unified panel yet |
|
||||
| Question graph (`QuestionGraphManager`) | experimental | Frontmatter classification + Mermaid evolution graph; not yet wired into the panel |
|
||||
| Vault-wide knowledge retrieval | not yet | Planned for v2 |
|
||||
| Execution plan / preview / rollback | not yet | Planned for v3 |
|
||||
One unified AI entry point connects all three: ask questions, generate structured notes, build execution plans, and keep everything synced — all through natural language.
|
||||
|
||||
> [!warning] Beta
|
||||
> v2.0.0 introduces significant architectural changes. The frontmatter schema and scheduled-task model are not yet stable. Back up your vault before upgrading.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## Core Modules
|
||||
|
||||
### AI Assistant (unified entry)
|
||||
### 1. Knowledge
|
||||
|
||||
Select text or place your cursor, then describe what you want in natural language:
|
||||
Build and maintain a structured knowledge base.
|
||||
|
||||
- **Expand / rewrite** selected text
|
||||
- **Explain** a term
|
||||
- **Generate diagrams** (flowchart, sequence, state, class, ER, Gantt) and LaTeX formulas
|
||||
- **Fill empty sections** based on document context
|
||||
- **Continue writing** from the cursor
|
||||
- **Summarize** the current document
|
||||
- **Beautify** existing content with callouts, links, and visual breaks
|
||||
- **Ask questions** and generate Q&A notes with automatic concept extraction.
|
||||
- **Classify questions** into new / refinement / expansion and maintain a question evolution graph.
|
||||
- **Create and complete concept pages** with definitions, examples, relations, and domain MOC indexes.
|
||||
- **Build reading projects** — generate a book skeleton, chapter pre-reading questions, summaries, and Feynman tests.
|
||||
- **Search your vault** and get answers with `[[source]]` references (metadata-based index, no embeddings).
|
||||
- **Detect knowledge debt** — empty concepts, orphan questions, unfinished readings, stale drafts.
|
||||
|
||||
Quick tags work too: `[扩写]` `[解释]` `[画图]` `[补全]` `[续写]` `[总结]` `[公式]` `[时序图]`.
|
||||
### 2. Execution
|
||||
|
||||
### Structured output
|
||||
Turn knowledge into reviewable actions.
|
||||
|
||||
Model output is post-processed for knowledge-base style: short paragraphs, Obsidian callouts (`> [!summary]`, `> [!warning]`, `> [!tip]`), Mermaid diagrams where appropriate, and automatic `[[concept]]` linking against existing pages. The output style is configurable: technical, minimal, academic, product, story, dashboard.
|
||||
- **Generate execution plans** from notes — preview all vault modifications before applying.
|
||||
- **Record execution logs** under `Knowledge/_Executions/` with full operation details.
|
||||
- **Scheduled tasks** (MVP) — periodic knowledge-debt scans and backup jobs.
|
||||
- **Safety by default** — AI-generated writes require confirmation; batch ops capped; high-risk plans force double-confirm.
|
||||
- Future: diff preview, rollback, task-plugin integrations.
|
||||
|
||||
### Reading projects
|
||||
### 3. Auxiliary
|
||||
|
||||
Turn any book into a structured study plan:
|
||||
Keep the system usable across devices and providers.
|
||||
|
||||
1. Enter book title (and optionally a table of contents).
|
||||
2. The plugin generates a reading roadmap, chapter relationships, and pre-reading questions.
|
||||
3. Take notes per chapter, then generate chapter summaries and Feynman tests.
|
||||
|
||||
### Knowledge organization
|
||||
|
||||
- New concepts captured under `Knowledge/Concepts/_未分类/`.
|
||||
- After completion, concepts are reorganized into domain subfolders.
|
||||
- Domain MOC index pages are generated with Mermaid overview graphs.
|
||||
- Question evolution graphs live in the question index.
|
||||
|
||||
### Baidu Cloud sync (optional)
|
||||
|
||||
- Incremental backup, bidirectional sync, or force overwrite.
|
||||
- Optional backup of the plugin itself and Obsidian config (toolbar, hotkeys, appearance).
|
||||
- Optional auto-backup after note generation.
|
||||
|
||||
> [!info] Privacy
|
||||
> AI features send your selection and parts of the active note to the configured chat-completions endpoint. Sync features upload selected notes (and optionally Obsidian config) to your own Baidu Pan storage. See [PRIVACY.md](./PRIVACY.md) for the full data flow.
|
||||
- **OpenAI-compatible LLM provider** — DeepSeek default; change Base URL for others.
|
||||
- **Configurable output styles** — knowledge-base, technical, minimal, product, academic, story, dashboard.
|
||||
- **Optional Baidu Cloud sync** — incremental backup, bidirectional sync, plugin and Obsidian config backup.
|
||||
- **Diagnostics** — privacy overview, config export, index rebuild, log cleanup.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
## Status
|
||||
|
||||
- Obsidian 1.7.2 or later.
|
||||
- A DeepSeek API key (or any OpenAI-compatible endpoint).
|
||||
- Baidu Cloud sync (optional): a Baidu Pan App ID and App Secret.
|
||||
| Module | Feature | Status | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Knowledge | AI Assistant | Stable | Insert / replace / append / show via unified entry |
|
||||
| Knowledge | Reading Projects | Stable | Skeleton, chapter questions, summaries, Feynman |
|
||||
| Knowledge | Concept Completion | Experimental | Exposed in command panel, preview before write |
|
||||
| Knowledge | Question Graph | Experimental | Classification + index + Mermaid evolution graph |
|
||||
| Knowledge | Vault QA | Experimental | Metadata-index retrieval, cited answers, no embeddings |
|
||||
| Knowledge | Knowledge Debt | Experimental | Dashboard with empty/orphan/unfinished/stale stats |
|
||||
| Execution | Execution Plan | Experimental | PlanBuilder + PlanExecutor, no rollback yet |
|
||||
| Execution | Scheduled Tasks | Planned | Scheduler types defined, runner in progress |
|
||||
| Auxiliary | Baidu Sync | Stable | Manual/auto backup and config sync |
|
||||
| Auxiliary | Multi-provider LLM | Partial | OpenAI-compatible base URL supported |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Install the plugin (see [Installation](#installation)).
|
||||
2. Go to **Settings → IStart-Note-AI → Auxiliary → AI Service** and enter your API key.
|
||||
3. Click the 🧠 ribbon icon or press the command palette → **IStart-Note-AI: AI 助手**.
|
||||
4. Type a request in natural language.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### From community plugins (preferred)
|
||||
|
||||
Submission to the community plugin store is in progress. Once available:
|
||||
### From community plugins (once available)
|
||||
|
||||
1. Settings → Community plugins → Browse.
|
||||
2. Search **IStart-Note-AI**.
|
||||
3. Install → Enable.
|
||||
|
||||
### Manual install (recommended path during beta)
|
||||
### Manual (recommended during beta)
|
||||
|
||||
Download the assets from a published [GitHub Release](https://github.com/yan-istart/IStart-Note-AI-Plugin/releases) — `main.js`, `manifest.json`, `styles.css` — and place them under `<your-vault>/.obsidian/plugins/istart-note-ai/`.
|
||||
Download `main.js`, `manifest.json`, `styles.css` from a [GitHub Release](https://github.com/yan-istart/IStart-Note-AI-Plugin/releases) and place them in `<vault>/.obsidian/plugins/istart-note-ai/`.
|
||||
|
||||
> Don't copy the source repository directly: the runtime bundle is built into `dist/` and is not committed to git. Always use the release assets.
|
||||
> Don't clone the source repo — the bundle lives in `dist/` and is not committed. Use release assets.
|
||||
|
||||
### Build from source
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build
|
||||
# dist/main.js, dist/manifest.json, dist/styles.css are the install artifacts
|
||||
# → dist/main.js, dist/manifest.json, dist/styles.css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings → IStart-Note-AI:
|
||||
Settings are organized into three tabs:
|
||||
|
||||
| Setting | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| API Key | DeepSeek API key (or compatible) | — |
|
||||
| Base URL | Chat completions endpoint root | `https://api.deepseek.com` |
|
||||
| Model | `deepseek-v4-flash` or `deepseek-v4-pro` | `deepseek-v4-flash` |
|
||||
| Output style | Knowledge-base, technical, minimal, product, academic, story, dashboard | Knowledge-base |
|
||||
| Q&A folder | Where Q&A notes are saved | `Knowledge/Q&A` |
|
||||
| Concepts folder | Where concept pages are saved | `Knowledge/Concepts` |
|
||||
| Baidu Cloud sync | Enable, App ID/Secret, remote path, auto-backup, ignore pattern | disabled |
|
||||
| Tab | Key settings |
|
||||
| --- | --- |
|
||||
| **Knowledge** | Q&A path, Concepts path, Questions index path, Reading path, auto-classify, concept depth, vault QA source limit |
|
||||
| **Execution** | Require preview, log path, scheduled tasks, safety policy |
|
||||
| **Auxiliary** | API key, Base URL, model, output style, Baidu sync, UI preferences |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -127,55 +129,86 @@ Settings → IStart-Note-AI:
|
|||
|
||||
### Desktop
|
||||
|
||||
- 🧠 **Ribbon icon** opens the command panel.
|
||||
- **Right-click in the editor** → `IStart-Note-AI: AI 助手`.
|
||||
- **Right-click a file in the sidebar** → `IStart-Note-AI: AI 助手`.
|
||||
- 🧠 **Ribbon icon** → command panel (Knowledge / Execution / Auxiliary).
|
||||
- **Right-click in editor** → `IStart-Note-AI: AI 助手` or `知识库问答`.
|
||||
- **Right-click file** → `IStart-Note-AI: AI 助手`.
|
||||
|
||||
### Mobile
|
||||
|
||||
- 🧠 **Ribbon icon** opens the command panel.
|
||||
- Add `AI 助手` to the mobile toolbar for a one-tap entry.
|
||||
|
||||
### Workflow
|
||||
|
||||
1. Optionally select text.
|
||||
2. Click 🧠 or right-click → `AI 助手`.
|
||||
3. Type a request (or use a quick tag, or leave blank for auto-detect).
|
||||
4. Preview the result, then choose to insert, replace, append, or show only.
|
||||
- 🧠 **Ribbon icon** → command panel.
|
||||
- Add commands to the mobile toolbar for one-tap access.
|
||||
|
||||
---
|
||||
|
||||
## Project layout
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
core/ # cross-feature infrastructure
|
||||
llm/ # unified LLM client + JSON extractor
|
||||
ai/ # AI features (assistant, classifier, planner, ...)
|
||||
features/ # UI and per-feature managers
|
||||
vault/ # vault writers
|
||||
settings/
|
||||
actions/ # action registry / command panel
|
||||
core/
|
||||
llm/ Unified LLM client + JSON extractor
|
||||
knowledge/ KnowledgeIndexService (metadata index)
|
||||
execution/ ExecutionPlan, PlanBuilder, PlanExecutor
|
||||
scheduler/ ScheduledTask types + runner (WIP)
|
||||
schema.ts SCHEMA_VERSION + helpers
|
||||
ai/ AI feature modules (assistant, classifier, planner, ...)
|
||||
features/
|
||||
assistant/ AI assistant modals
|
||||
concept/ Concept completion + page manager
|
||||
question/ Question classify + graph manager
|
||||
reading/ Reading project manager
|
||||
dashboard/ Knowledge debt modal
|
||||
sync/ Baidu sync
|
||||
command-panel/ Unified command panel
|
||||
vault/ Vault writer (conflict-safe)
|
||||
settings/ Settings tab (tabbed layout)
|
||||
actions/ Action registry + definitions
|
||||
main.ts
|
||||
```
|
||||
|
||||
The `core/llm` module centralizes every chat-completions call. New AI features should depend on it instead of calling `requestUrl` directly.
|
||||
---
|
||||
|
||||
## Privacy
|
||||
|
||||
AI features send your selection and partial note context to the configured chat-completions endpoint. Sync features upload to your own Baidu Pan. No telemetry. No plugin-operated servers. Full details in [PRIVACY.md](./PRIVACY.md).
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
- v1.9 (in progress): unified LLM client, basic vault retrieval, AI operation preview, full open-source governance.
|
||||
- v2.0: vault-wide knowledge retrieval with citations, question graph + concept maturity dashboards.
|
||||
- v3.0: execution engine — turn notes into reviewable, rollback-able plans (tasks, decisions, projects).
|
||||
### v2.0 — Knowledge System Foundation (current)
|
||||
|
||||
See [the analysis writeup](https://github.com/yan-istart/IStart-Note-AI-Plugin/issues) for the longer plan.
|
||||
- Three-module product structure: Knowledge / Execution / Auxiliary.
|
||||
- Vault-wide lightweight knowledge index.
|
||||
- Concept completion and question graph in command panel.
|
||||
- Knowledge debt dashboard.
|
||||
- Basic execution plan and execution logs.
|
||||
- Settings page with tabbed navigation.
|
||||
- Open-source governance and privacy docs.
|
||||
|
||||
### v2.1 — Execution MVP
|
||||
|
||||
- Scheduled tasks runtime (knowledge-debt scan, auto-backup).
|
||||
- Execution plan preview modal with diff.
|
||||
- Safer policy: AI writes always produce plan-only by default.
|
||||
- Execution history view.
|
||||
|
||||
### v2.2 — Trust & Control
|
||||
|
||||
- Rollback for recent executions.
|
||||
- More granular privacy controls.
|
||||
- Optional local vector index for richer vault QA.
|
||||
|
||||
### v3.0 — Integrations
|
||||
|
||||
- Tasks plugin / Periodic Notes integration.
|
||||
- GitHub Issues / Linear / Todoist export.
|
||||
- Multi-vault support.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
PRs and issues are welcome. Read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a change. For security reports, see [SECURITY.md](./SECURITY.md).
|
||||
PRs and issues welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md). Security issues: [SECURITY.md](./SECURITY.md).
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
227
README.zh-CN.md
227
README.zh-CN.md
|
|
@ -1,181 +1,182 @@
|
|||
# IStart-Note-AI
|
||||
|
||||
> [!warning] 测试版软件
|
||||
> IStart-Note-AI 仍在持续开发中。下文标记为**实验中**的功能可能在没有提前通知的情况下变更或移除。Frontmatter schema 尚未稳定,尝试新功能前请先备份你的 Vault。
|
||||
<p align="center">
|
||||
<strong>把 Obsidian 笔记变成"知识 → 执行"的个人系统。</strong>
|
||||
</p>
|
||||
|
||||
[English README →](./README.md)
|
||||
<p align="center">
|
||||
<a href="./README.md">English</a> ·
|
||||
<a href="#快速开始">快速开始</a> ·
|
||||
<a href="#隐私说明">隐私说明</a> ·
|
||||
<a href="#路线图">路线图</a>
|
||||
</p>
|
||||
|
||||
把笔记升级成可被结构化、可被检索、可被执行的个人知识系统的 Obsidian 插件。统一的 AI 助手帮你起草、扩写、组织笔记,并自动维护概念页、双链、阅读项目,可选搭配百度云同步。
|
||||
|
||||
底层使用 OpenAI 兼容的 chat completions 接口,默认接入 [DeepSeek](https://platform.deepseek.com),更换 Base URL 可使用其他兼容服务。
|
||||
<p align="center">
|
||||
<img alt="Version" src="https://img.shields.io/github/v/release/yan-istart/IStart-Note-AI-Plugin?include_prereleases">
|
||||
<img alt="License" src="https://img.shields.io/github/license/yan-istart/IStart-Note-AI-Plugin">
|
||||
<img alt="CI" src="https://img.shields.io/github/actions/workflow/status/yan-istart/IStart-Note-AI-Plugin/ci.yml?branch=main">
|
||||
<img alt="Obsidian" src="https://img.shields.io/badge/Obsidian-1.7.2%2B-7C3AED">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 功能状态总览
|
||||
IStart-Note-AI 是一个面向 Obsidian 的 AI 插件,围绕 **知识沉淀、执行计划、同步辅助** 三个模块,帮助你把零散笔记转化为可检索、可关联、可执行的个人知识系统。
|
||||
|
||||
| 功能 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| AI 助手(插入 / 替换 / 追加 / 仅展示) | 稳定 | 命令面板单一入口,配套内容分类 + 结构化 Prompt + Markdown 美化 |
|
||||
| 阅读项目 | 稳定 | 全书骨架、章节预设问题、章节总结、费曼测试 |
|
||||
| 百度云同步(笔记 + 插件配置) | 稳定 | 手动与自动模式、冲突策略、插件本身 / Obsidian 配置备份 |
|
||||
| 文档美化 | 稳定 | 重新组织标题,加入 Callout 和 Mermaid |
|
||||
| 概念页自动创建 | 稳定 | 从 `[[概念]]` 自动扫描并创建空概念页 |
|
||||
| 概念页智能补全(`ConceptCompleter`) | 实验中 | 内部实现已就绪,尚未接入命令面板 |
|
||||
| 问题图谱(`QuestionGraphManager`) | 实验中 | 已支持 frontmatter 分类与 Mermaid 演化图,尚未接入命令面板 |
|
||||
| Vault 级知识检索 | 未实现 | 列入 v2 |
|
||||
| 执行计划 / 预览 / 回滚 | 未实现 | 列入 v3 |
|
||||
一个统一 AI 入口,连接三类能力:知识沉淀、执行计划、辅助同步。
|
||||
|
||||
> [!warning] 测试版
|
||||
> v2.0.0 引入了重大架构变更。Frontmatter schema 和定时任务模型尚未稳定。升级前请备份 Vault。
|
||||
|
||||
---
|
||||
|
||||
## 主要功能
|
||||
## 三大模块
|
||||
|
||||
### AI 助手(统一入口)
|
||||
### 1. 知识 Knowledge
|
||||
|
||||
选中文字或停在光标处,用自然语言描述你想做的事:
|
||||
构建和维护结构化知识库。
|
||||
|
||||
- **扩写 / 改写**选中文字
|
||||
- **解释**某个术语
|
||||
- **生成图表**(流程图、时序图、状态图、类图、ER、甘特图)和 LaTeX 公式
|
||||
- **补全**当前空章节
|
||||
- **续写**光标后的内容
|
||||
- **总结**当前文档
|
||||
- **美化**已有内容(加 Callout、双链、视觉分隔)
|
||||
- **提问**并生成 Q&A 笔记,自动提取概念和关系。
|
||||
- **问题分类**:new / refinement / expansion,维护问题演化图。
|
||||
- **概念页创建与补全**:定义、解释、示例、关联概念、领域 MOC 索引。
|
||||
- **阅读项目**:全书骨架、章节预设问题、章节总结、费曼测试。
|
||||
- **知识库问答**:基于 Vault 索引检索回答,附带 `[[来源]]` 引用。
|
||||
- **知识债务看板**:空概念、孤立问题、未完成阅读、长期草稿一目了然。
|
||||
|
||||
也支持快捷标签:`[扩写]` `[解释]` `[画图]` `[补全]` `[续写]` `[总结]` `[公式]` `[时序图]`。
|
||||
### 2. 执行 Execution
|
||||
|
||||
### 结构化输出
|
||||
把知识转化为可审查的行动。
|
||||
|
||||
模型输出会做一层后处理,按知识库风格统一:短段落、Obsidian Callout(`> [!summary]`、`> [!warning]`、`> [!tip]`)、必要时的 Mermaid 图,以及对已有概念页自动加 `[[双链]]`。输出风格可切换:technical / minimal / academic / product / story / dashboard。
|
||||
- **生成执行计划**:预览所有 Vault 修改后再执行。
|
||||
- **执行日志**:自动记录到 `Knowledge/_Executions/`。
|
||||
- **定时任务**(MVP):每日知识债务扫描、自动百度备份。
|
||||
- **安全优先**:AI 写入需要确认,批量操作有上限,高风险计划强制二次确认。
|
||||
- 未来:diff 预览、回滚、任务集成。
|
||||
|
||||
### 阅读项目
|
||||
### 3. 辅助 Auxiliary
|
||||
|
||||
把一本书变成一份可执行的阅读计划:
|
||||
跨设备和多服务的基础支撑。
|
||||
|
||||
1. 输入书名(可选粘贴目录)。
|
||||
2. 插件生成阅读路线图、章节关系、读前问题。
|
||||
3. 边读边记,再生成章节总结与费曼测试。
|
||||
|
||||
### 知识组织
|
||||
|
||||
- 新概念先落到 `Knowledge/Concepts/_未分类/`。
|
||||
- 补全完成后,按 domain 自动归类到子目录。
|
||||
- 自动维护 domain MOC 索引页(含 Mermaid 概览图)。
|
||||
- 问题索引下生成问题演化图。
|
||||
|
||||
### 百度云同步(可选)
|
||||
|
||||
- 增量备份 / 双向同步 / 强制覆盖。
|
||||
- 可选连同插件本身及 Obsidian 配置(工具栏、快捷键、外观)一起备份。
|
||||
- 可选生成笔记后自动备份。
|
||||
|
||||
> [!info] 隐私说明
|
||||
> AI 功能会把你选中的内容以及当前笔记的部分上下文发送到所配置的 chat-completions 接口。同步功能会把笔记(可选 Obsidian 配置)上传到你自己的百度网盘。完整数据流见 [PRIVACY.md](./PRIVACY.md)。
|
||||
- **OpenAI 兼容 LLM**:默认 DeepSeek,切换 Base URL 可用其他服务。
|
||||
- **输出风格可选**:知识库、技术、极简、产品、学术、叙事、仪表盘。
|
||||
- **百度网盘同步**(可选):增量备份、双向同步、插件和 Obsidian 配置备份。
|
||||
- **诊断与隐私**:查看数据流说明、导出配置、重建索引、清理日志。
|
||||
|
||||
---
|
||||
|
||||
## 环境要求
|
||||
## 状态
|
||||
|
||||
- Obsidian 1.7.2 及以上。
|
||||
- DeepSeek 或其它 OpenAI 兼容服务的 API Key。
|
||||
- 百度云同步(可选):百度网盘开放平台 App ID 与 App Secret。
|
||||
| 模块 | 功能 | 状态 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 知识 | AI 助手 | 稳定 | 插入 / 替换 / 追加 / 仅展示 |
|
||||
| 知识 | 阅读项目 | 稳定 | 骨架、章节问题、总结、费曼 |
|
||||
| 知识 | 概念页补全 | 实验中 | 已接入命令面板,预览后写入 |
|
||||
| 知识 | 问题图谱 | 实验中 | 分类 + 索引 + Mermaid 演化图 |
|
||||
| 知识 | 知识库问答 | 实验中 | 元数据索引检索,无 embedding |
|
||||
| 知识 | 知识债务 | 实验中 | 空概念 / 孤立问题 / 草稿统计 |
|
||||
| 执行 | 执行计划 | 实验中 | PlanBuilder + Executor,无回滚 |
|
||||
| 执行 | 定时任务 | 规划中 | 类型定义完成,runner 开发中 |
|
||||
| 辅助 | 百度同步 | 稳定 | 手动/自动备份与配置同步 |
|
||||
| 辅助 | 多 Provider | 部分 | 支持 OpenAI 兼容 Base URL |
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
1. 安装插件(见下方安装说明)。
|
||||
2. 进入**设置 → IStart-Note-AI → 辅助 → AI 服务**,输入 API Key。
|
||||
3. 点击侧边栏 🧠 图标或命令面板 → **IStart-Note-AI: AI 助手**。
|
||||
4. 用自然语言输入指令。
|
||||
|
||||
---
|
||||
|
||||
## 安装
|
||||
|
||||
### 从社区插件商店(首选)
|
||||
### 社区插件商店(审核后可用)
|
||||
|
||||
正在准备提交。审核通过后:
|
||||
1. 设置 → 第三方插件 → 浏览 → 搜索 **IStart-Note-AI**。
|
||||
2. 安装 → 启用。
|
||||
|
||||
1. 设置 → 第三方插件 → 浏览。
|
||||
2. 搜索 **IStart-Note-AI**。
|
||||
3. 安装 → 启用。
|
||||
### 手动安装(测试期推荐)
|
||||
|
||||
### 手动安装(测试版推荐方式)
|
||||
|
||||
从 [GitHub Release](https://github.com/yan-istart/IStart-Note-AI-Plugin/releases) 下载 `main.js`、`manifest.json`、`styles.css`,放到 `<你的 Vault>/.obsidian/plugins/istart-note-ai/` 目录下。
|
||||
|
||||
> 不要直接克隆源码安装:构建产物在 `dist/`,并不入库。请始终使用 release 资产。
|
||||
从 [GitHub Release](https://github.com/yan-istart/IStart-Note-AI-Plugin/releases) 下载 `main.js`、`manifest.json`、`styles.css`,放到 `<Vault>/.obsidian/plugins/istart-note-ai/`。
|
||||
|
||||
### 从源码构建
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build
|
||||
# dist/main.js、dist/manifest.json、dist/styles.css 即为安装文件
|
||||
npm ci && npm run build
|
||||
# → dist/main.js, dist/manifest.json, dist/styles.css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设置项
|
||||
## 设置
|
||||
|
||||
设置 → IStart-Note-AI:
|
||||
设置页按三个标签组织:
|
||||
|
||||
| 设置 | 说明 | 默认 |
|
||||
| --- | --- | --- |
|
||||
| API Key | DeepSeek API Key(或兼容服务) | — |
|
||||
| Base URL | chat completions 根地址 | `https://api.deepseek.com` |
|
||||
| 模型 | `deepseek-v4-flash` 或 `deepseek-v4-pro` | `deepseek-v4-flash` |
|
||||
| 输出风格 | 知识库、技术、极简、产品、学术、故事、仪表盘 | 知识库 |
|
||||
| 问答目录 | Q&A 笔记保存路径 | `Knowledge/Q&A` |
|
||||
| 概念目录 | 概念页保存路径 | `Knowledge/Concepts` |
|
||||
| 百度云同步 | 启用、App ID/Secret、远端路径、自动备份、忽略规则 | 关闭 |
|
||||
| 标签 | 主要设置 |
|
||||
| --- | --- |
|
||||
| **知识** | Q&A 路径、概念路径、问题索引路径、知识索引状态与重建 |
|
||||
| **执行** | 执行日志目录、安全策略、定时任务(v2.1) |
|
||||
| **辅助** | API Key、Base URL、模型、输出风格、百度同步、隐私说明 |
|
||||
|
||||
---
|
||||
|
||||
## 使用方式
|
||||
## 使用
|
||||
|
||||
### 桌面端
|
||||
### 桌面
|
||||
|
||||
- 🧠 **侧边栏图标**:打开命令面板。
|
||||
- **编辑器右键** → `IStart-Note-AI: AI 助手`。
|
||||
- **文件列表右键** → `IStart-Note-AI: AI 助手`。
|
||||
- 🧠 侧边栏图标 → 命令面板(知识 / 执行 / 辅助)。
|
||||
- 编辑器右键 → `IStart-Note-AI: AI 助手` 或 `知识库问答`。
|
||||
- 文件列表右键 → `IStart-Note-AI: AI 助手`。
|
||||
|
||||
### 移动端
|
||||
|
||||
- 🧠 **侧边栏图标**:打开命令面板。
|
||||
- 把 `AI 助手` 添加到移动工具栏,可一键唤起。
|
||||
|
||||
### 工作流
|
||||
|
||||
1. 可选:选中文字。
|
||||
2. 点击 🧠 或右键 → AI 助手。
|
||||
3. 输入指令(或用快捷标签,或留空让插件自动判断)。
|
||||
4. 预览结果,选择插入 / 替换 / 追加 / 仅展示。
|
||||
- 🧠 侧边栏图标 → 命令面板。
|
||||
- 把命令添加到移动工具栏。
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
## 隐私说明
|
||||
|
||||
```
|
||||
src/
|
||||
core/ # 跨功能基础设施
|
||||
llm/ # 统一 LLM 客户端 + JSON 提取
|
||||
ai/ # AI 功能(助手、分类器、Planner ...)
|
||||
features/ # 各功能 UI 与管理器
|
||||
vault/ # Vault 写入层
|
||||
settings/
|
||||
actions/ # action 注册 / 命令面板
|
||||
main.ts
|
||||
```
|
||||
|
||||
`core/llm` 是所有 chat-completions 调用的唯一入口。新增 AI 功能应该依赖它,而不是直接调用 `requestUrl`。
|
||||
AI 功能会把你选中的内容和部分笔记上下文发送到所配置的 API 端点。同步功能只上传到你自己的百度网盘。无遥测、无插件方服务器。详见 [PRIVACY.md](./PRIVACY.md) / [PRIVACY.zh-CN.md](./PRIVACY.zh-CN.md)。
|
||||
|
||||
---
|
||||
|
||||
## 路线图
|
||||
|
||||
- v1.9(进行中):统一 LLM 客户端、基础 Vault 检索、AI 操作预览、补齐开源治理。
|
||||
- v2.0:带来源引用的 Vault 级检索、问题图谱与概念成熟度看板。
|
||||
- v3.0:执行引擎 —— 把笔记转换为可预览、可回滚的计划(任务、决策、项目)。
|
||||
### v2.0 — 知识系统基础版(当前)
|
||||
|
||||
更长期规划见仓库 issue。
|
||||
- 三模块产品结构:知识 / 执行 / 辅助。
|
||||
- Vault 级轻量知识索引。
|
||||
- 概念补全和问题图谱接入命令面板。
|
||||
- 知识债务看板。
|
||||
- 基础执行计划和执行日志。
|
||||
- 分组设置页。
|
||||
- 开源治理和隐私文档。
|
||||
|
||||
### v2.1 — 执行 MVP
|
||||
|
||||
- 定时任务运行时。
|
||||
- 执行计划预览 Modal + diff。
|
||||
- AI 写入默认 plan-only。
|
||||
- 执行历史视图。
|
||||
|
||||
### v2.2 — 信任与控制
|
||||
|
||||
- 回滚最近执行。
|
||||
- 更细粒度的隐私控制。
|
||||
- 可选本地向量索引。
|
||||
|
||||
### v3.0 — 集成
|
||||
|
||||
- Tasks / Periodic Notes 集成。
|
||||
- GitHub Issues / Linear / Todoist 导出。
|
||||
|
||||
---
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提 Issue / PR。请先阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。安全相关问题请按 [SECURITY.md](./SECURITY.md) 流程提交。
|
||||
欢迎提 Issue / PR。请先阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。安全问题见 [SECURITY.md](./SECURITY.md)。
|
||||
|
||||
## 协议
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "istart-note-ai",
|
||||
"name": "IStart-Note-AI",
|
||||
"version": "1.9.0",
|
||||
"version": "2.0.0",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Generate structured knowledge notes from questions and selected text using DeepSeek AI, with automatic concept pages, bidirectional links, and a question graph.",
|
||||
"author": "Yan",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "istart-note-ai",
|
||||
"version": "1.9.0",
|
||||
"version": "2.0.0",
|
||||
"description": "IStart-Note-AI: an Obsidian plugin that turns notes into a structured personal knowledge system, powered by DeepSeek and other OpenAI-compatible LLMs.",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -1,31 +1,49 @@
|
|||
import { ActionDef } from "./types";
|
||||
|
||||
/**
|
||||
* 精简后的动作定义。
|
||||
* 只保留 3 个核心入口,其他全部通过"AI 助手"自由指令覆盖。
|
||||
* All actions, organized by domain: Knowledge / Execution / Auxiliary.
|
||||
*
|
||||
* The "AI 助手" is placed in Auxiliary as a cross-cutting entry point,
|
||||
* but the command panel renders it as a pinned top-level button.
|
||||
*/
|
||||
export const ALL_ACTIONS: ActionDef[] = [
|
||||
// ── 核心入口:AI 助手(覆盖 90% 场景) ────────────────────
|
||||
{
|
||||
id: "ai-assistant",
|
||||
label: "AI 助手",
|
||||
icon: "sparkles",
|
||||
description: "选中文字或输入指令,AI 智能执行",
|
||||
group: "general",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu", "file-menu"],
|
||||
run: (ctx) => { ctx.plugin.openAssistant(); },
|
||||
},
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// KNOWLEDGE
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ── 概念页 ────────────────────────────────────────────────
|
||||
{
|
||||
id: "vault-qa",
|
||||
label: "知识库问答",
|
||||
icon: "library",
|
||||
description: "基于 Vault 检索回答,附带来源引用",
|
||||
domain: "knowledge",
|
||||
section: "retrieval",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu"],
|
||||
experimental: true,
|
||||
run: (ctx) => { ctx.plugin.openVaultQA(); },
|
||||
},
|
||||
{
|
||||
id: "question-with-graph",
|
||||
label: "知识提问",
|
||||
icon: "help-circle",
|
||||
description: "提问 → 自动分类 → 生成 Q&A → 更新问题图谱",
|
||||
domain: "knowledge",
|
||||
section: "question",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openQuestionWithGraph(); },
|
||||
},
|
||||
{
|
||||
id: "complete-current-concept",
|
||||
label: "补全当前概念页",
|
||||
icon: "book-plus",
|
||||
description: "为当前打开的空概念页生成定义、解释、示例、关联",
|
||||
group: "concept",
|
||||
domain: "knowledge",
|
||||
section: "concept",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu"],
|
||||
experimental: true,
|
||||
run: (ctx) => { ctx.plugin.openCompleteCurrentConcept(); },
|
||||
},
|
||||
{
|
||||
|
|
@ -33,81 +51,79 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
label: "扫描并补全空概念页",
|
||||
icon: "search",
|
||||
description: "扫描 Vault 中所有空概念页,批量补全",
|
||||
group: "concept",
|
||||
domain: "knowledge",
|
||||
section: "concept",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
experimental: true,
|
||||
run: (ctx) => { ctx.plugin.openScanEmptyConcepts(); },
|
||||
},
|
||||
|
||||
// ── 知识提问(带图谱) ────────────────────────────────────
|
||||
{
|
||||
id: "question-with-graph",
|
||||
label: "知识提问",
|
||||
icon: "help-circle",
|
||||
description: "提问 → 自动分类 → 生成 Q&A → 更新问题图谱",
|
||||
group: "general",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openQuestionWithGraph(); },
|
||||
},
|
||||
|
||||
// ── 知识库问答(带引用) ──────────────────────────────────
|
||||
{
|
||||
id: "vault-qa",
|
||||
label: "知识库问答",
|
||||
icon: "library",
|
||||
description: "基于 Vault 检索回答,附带来源引用",
|
||||
group: "general",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu"],
|
||||
run: (ctx) => { ctx.plugin.openVaultQA(); },
|
||||
},
|
||||
|
||||
// ── 阅读项目(需要专门表单) ──────────────────────────────
|
||||
{
|
||||
id: "new-reading-project",
|
||||
label: "新建阅读项目",
|
||||
icon: "book-open",
|
||||
description: "输入书名,生成阅读地图",
|
||||
group: "reading",
|
||||
domain: "knowledge",
|
||||
section: "reading",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openNewReadingProject(); },
|
||||
},
|
||||
|
||||
// ── 知识债务看板 ──────────────────────────────────────────
|
||||
{
|
||||
id: "knowledge-debt",
|
||||
label: "知识债务看板",
|
||||
icon: "bar-chart-2",
|
||||
description: "查看空概念、孤立问题、未完成阅读、长期未更新草稿",
|
||||
group: "general",
|
||||
description: "空概念、孤立问题、未完成阅读、长期未更新草稿",
|
||||
domain: "knowledge",
|
||||
section: "debt",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
experimental: true,
|
||||
run: (ctx) => { ctx.plugin.openKnowledgeDebt(); },
|
||||
},
|
||||
|
||||
// ── 百度云同步(独立功能) ────────────────────────────────
|
||||
{
|
||||
id: "baidu-sync",
|
||||
label: "百度云同步",
|
||||
icon: "cloud",
|
||||
description: "备份 / 恢复 / 同步",
|
||||
group: "sync",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openBaiduSyncModal(); },
|
||||
},
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// EXECUTION
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ── 美化当前文档 ──────────────────────────────────────────
|
||||
// Placeholder: future actions like "生成执行计划", "查看执行日志", "定时任务"
|
||||
// will be added here once runtime is implemented.
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// AUXILIARY
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
{
|
||||
id: "ai-assistant",
|
||||
label: "AI 助手",
|
||||
icon: "sparkles",
|
||||
description: "选中文字或输入指令,AI 智能执行(跨模块入口)",
|
||||
domain: "auxiliary",
|
||||
section: "assistant",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu", "file-menu"],
|
||||
run: (ctx) => { ctx.plugin.openAssistant(); },
|
||||
},
|
||||
{
|
||||
id: "beautify-note",
|
||||
label: "美化当前文档",
|
||||
icon: "wand",
|
||||
description: "整理结构、插入 Callout、生成双链",
|
||||
group: "document",
|
||||
domain: "auxiliary",
|
||||
section: "document",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu", "file-menu"],
|
||||
run: (ctx) => { void ctx.plugin.beautifyCurrentNote(); },
|
||||
},
|
||||
{
|
||||
id: "baidu-sync",
|
||||
label: "百度云同步",
|
||||
icon: "cloud",
|
||||
description: "备份 / 恢复 / 同步",
|
||||
domain: "auxiliary",
|
||||
section: "sync",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openBaiduSyncModal(); },
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Notice, TFile } from "obsidian";
|
||||
import type DeepSeekPlugin from "../main";
|
||||
import { ActionDef, ActionContext, GROUP_TITLES, GROUP_ORDER } from "./types";
|
||||
import { ActionDef, ActionContext, DOMAIN_TITLES, DOMAIN_ORDER } from "./types";
|
||||
import { CommandPanelModal } from "../features/command-panel/CommandPanelModal";
|
||||
import type { PanelGroup, PanelAction } from "../features/command-panel/CommandPanelModal";
|
||||
|
||||
|
|
@ -96,17 +96,17 @@ function openPanel(plugin: DeepSeekPlugin, actions: ActionDef[]) {
|
|||
}
|
||||
|
||||
const groups: PanelGroup[] = [];
|
||||
for (const groupId of GROUP_ORDER) {
|
||||
const groupActions = actions.filter(
|
||||
(a) => a.group === groupId && a.showIn.includes("panel") && evaluateWhen(a.when, ctx)
|
||||
for (const domainId of DOMAIN_ORDER) {
|
||||
const domainActions = actions.filter(
|
||||
(a) => a.domain === domainId && a.showIn.includes("panel") && evaluateWhen(a.when, ctx)
|
||||
);
|
||||
if (groupActions.length === 0) continue;
|
||||
if (domainActions.length === 0) continue;
|
||||
groups.push({
|
||||
title: GROUP_TITLES[groupId],
|
||||
actions: groupActions.map((a) => ({
|
||||
title: DOMAIN_TITLES[domainId],
|
||||
actions: domainActions.map((a) => ({
|
||||
id: a.id,
|
||||
icon: a.icon,
|
||||
label: a.label,
|
||||
label: a.label + (a.experimental ? " ⚗️" : ""),
|
||||
description: a.description,
|
||||
callback: () => a.run(ctx),
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -7,30 +7,44 @@ export interface ActionContext {
|
|||
app: App;
|
||||
editor: Editor | null;
|
||||
activeFile: TFile | null;
|
||||
selection: string; // 选中文字(trim 后)
|
||||
fileContent: string; // 当前文件全文
|
||||
fileType: string | undefined; // frontmatter.type
|
||||
filePath: string; // 当前文件路径
|
||||
sectionName: string | null; // 光标所在 section 名
|
||||
// file-menu 专用:右键的目标文件(可能不是当前打开的文件)
|
||||
selection: string;
|
||||
fileContent: string;
|
||||
fileType: string | undefined;
|
||||
filePath: string;
|
||||
sectionName: string | null;
|
||||
targetFile: TFile | null;
|
||||
}
|
||||
|
||||
/** 可见性条件 */
|
||||
export interface ActionWhen {
|
||||
always?: boolean; // 始终可见
|
||||
hasSelection?: boolean; // 需要有选中文字
|
||||
noSelection?: boolean; // 需要没有选中文字
|
||||
fileType?: string[]; // frontmatter type 匹配其一
|
||||
filePath?: string; // 文件路径包含此字符串
|
||||
inSection?: boolean; // 光标在某个 ## section 内
|
||||
always?: boolean;
|
||||
hasSelection?: boolean;
|
||||
noSelection?: boolean;
|
||||
fileType?: string[];
|
||||
filePath?: string;
|
||||
inSection?: boolean;
|
||||
}
|
||||
|
||||
/** 动作出现的入口 */
|
||||
export type ActionEntry = "panel" | "editor-menu" | "file-menu";
|
||||
|
||||
/** 面板分组 */
|
||||
export type ActionGroup = "general" | "selection" | "edit" | "concept" | "reading" | "sync" | "document";
|
||||
/** 三大产品域 */
|
||||
export type ActionDomain = "knowledge" | "execution" | "auxiliary";
|
||||
|
||||
/** 细分领域(用于面板二级分组、设置定位等) */
|
||||
export type ActionSection =
|
||||
| "question"
|
||||
| "concept"
|
||||
| "reading"
|
||||
| "retrieval"
|
||||
| "debt"
|
||||
| "plan"
|
||||
| "scheduler"
|
||||
| "logs"
|
||||
| "sync"
|
||||
| "assistant"
|
||||
| "document"
|
||||
| "settings";
|
||||
|
||||
/** 动作定义 */
|
||||
export interface ActionDef {
|
||||
|
|
@ -38,24 +52,33 @@ export interface ActionDef {
|
|||
label: string;
|
||||
icon: string;
|
||||
description?: string;
|
||||
group: ActionGroup;
|
||||
domain: ActionDomain;
|
||||
section: ActionSection;
|
||||
when: ActionWhen;
|
||||
showIn: ActionEntry[];
|
||||
/** 操作风险 */
|
||||
risk?: "none" | "low" | "medium" | "high";
|
||||
/** 是否实验性功能 */
|
||||
experimental?: boolean;
|
||||
run: (ctx: ActionContext) => void;
|
||||
}
|
||||
|
||||
/** 分组标题映射 */
|
||||
export const GROUP_TITLES: Record<ActionGroup, string> = {
|
||||
general: "通用",
|
||||
selection: "选中文字",
|
||||
edit: "编辑",
|
||||
concept: "概念页",
|
||||
reading: "阅读",
|
||||
sync: "同步",
|
||||
document: "文档工具",
|
||||
// ── 兼容层:旧 group 映射到新 domain ──────────────────────────
|
||||
// 保留旧类型名以便 registry 和 panel 平滑迁移
|
||||
|
||||
/** @deprecated Use ActionDomain + ActionSection */
|
||||
export type ActionGroup = ActionDomain;
|
||||
|
||||
/** 域标题(面板一级分组) */
|
||||
export const DOMAIN_TITLES: Record<ActionDomain, string> = {
|
||||
knowledge: "知识",
|
||||
execution: "执行",
|
||||
auxiliary: "辅助",
|
||||
};
|
||||
|
||||
/** 分组排序 */
|
||||
export const GROUP_ORDER: ActionGroup[] = [
|
||||
"general", "selection", "edit", "concept", "reading", "sync", "document",
|
||||
];
|
||||
/** 域排序 */
|
||||
export const DOMAIN_ORDER: ActionDomain[] = ["knowledge", "execution", "auxiliary"];
|
||||
|
||||
// ── 向后兼容的 GROUP 导出(registry.ts 还在用) ───────────────
|
||||
export const GROUP_TITLES = DOMAIN_TITLES;
|
||||
export const GROUP_ORDER = DOMAIN_ORDER;
|
||||
|
|
|
|||
|
|
@ -208,6 +208,11 @@ export class KnowledgeIndexService {
|
|||
return this.entries.size;
|
||||
}
|
||||
|
||||
/** Get all entries as an array. */
|
||||
getAll(): IndexEntry[] {
|
||||
return [...this.entries.values()];
|
||||
}
|
||||
|
||||
// ── Internals ──────────────────────────────────────────────
|
||||
|
||||
private indexFile(file: TFile): void {
|
||||
|
|
|
|||
68
src/core/scheduler/NextRunCalculator.ts
Normal file
68
src/core/scheduler/NextRunCalculator.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { ScheduleTrigger } from "./types";
|
||||
|
||||
/**
|
||||
* Determine whether a task is due and calculate the next run time.
|
||||
*/
|
||||
export class NextRunCalculator {
|
||||
/** Check if the task should fire now. */
|
||||
isDue(trigger: ScheduleTrigger, lastRunAt: string | undefined, now: Date): boolean {
|
||||
switch (trigger.type) {
|
||||
case "on-startup":
|
||||
// Fire once per session — if never run this session
|
||||
return !lastRunAt;
|
||||
|
||||
case "interval": {
|
||||
if (!lastRunAt) return true;
|
||||
const elapsed = now.getTime() - new Date(lastRunAt).getTime();
|
||||
return elapsed >= trigger.minutes * 60_000;
|
||||
}
|
||||
|
||||
case "daily": {
|
||||
const [h, m] = trigger.time.split(":").map(Number);
|
||||
if (now.getHours() < h || (now.getHours() === h && now.getMinutes() < m)) return false;
|
||||
if (!lastRunAt) return true;
|
||||
const lastDate = new Date(lastRunAt).toDateString();
|
||||
return lastDate !== now.toDateString();
|
||||
}
|
||||
|
||||
case "weekly": {
|
||||
if (now.getDay() !== trigger.weekday) return false;
|
||||
const [h, m] = trigger.time.split(":").map(Number);
|
||||
if (now.getHours() < h || (now.getHours() === h && now.getMinutes() < m)) return false;
|
||||
if (!lastRunAt) return true;
|
||||
const elapsed = now.getTime() - new Date(lastRunAt).getTime();
|
||||
return elapsed >= 6 * 24 * 60 * 60_000; // at least 6 days since last
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Calculate the next scheduled run (for display). */
|
||||
getNextRun(trigger: ScheduleTrigger, now: Date): Date {
|
||||
switch (trigger.type) {
|
||||
case "on-startup":
|
||||
return now; // always "next startup"
|
||||
|
||||
case "interval": {
|
||||
return new Date(now.getTime() + trigger.minutes * 60_000);
|
||||
}
|
||||
|
||||
case "daily": {
|
||||
const [h, m] = trigger.time.split(":").map(Number);
|
||||
const next = new Date(now);
|
||||
next.setHours(h, m, 0, 0);
|
||||
if (next <= now) next.setDate(next.getDate() + 1);
|
||||
return next;
|
||||
}
|
||||
|
||||
case "weekly": {
|
||||
const [h, m] = trigger.time.split(":").map(Number);
|
||||
const next = new Date(now);
|
||||
const diff = (trigger.weekday - now.getDay() + 7) % 7 || 7;
|
||||
next.setDate(now.getDate() + diff);
|
||||
next.setHours(h, m, 0, 0);
|
||||
if (next <= now) next.setDate(next.getDate() + 7);
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/core/scheduler/ScheduledTaskRunner.ts
Normal file
150
src/core/scheduler/ScheduledTaskRunner.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import { Notice } from "obsidian";
|
||||
import type DeepSeekPlugin from "../../main";
|
||||
import { ScheduledTaskConfig, ScheduledTaskResult } from "./types";
|
||||
import { NextRunCalculator } from "./NextRunCalculator";
|
||||
import { PlanExecutor } from "../execution";
|
||||
import { PlanBuilder } from "../execution";
|
||||
import { KnowledgeIndexService } from "../knowledge";
|
||||
import { todayIso } from "../schema";
|
||||
|
||||
/**
|
||||
* ScheduledTaskRunner — checks due tasks every 60s while Obsidian is open.
|
||||
*
|
||||
* Safety:
|
||||
* - "notify-only": shows a Notice and optionally writes a report.
|
||||
* - "create-plan-only": generates an ExecutionPlan draft but does NOT apply.
|
||||
* - "auto-execute-low-risk": applies only if plan.riskLevel === "low".
|
||||
*/
|
||||
export class ScheduledTaskRunner {
|
||||
private running = new Set<string>();
|
||||
private calc = new NextRunCalculator();
|
||||
private intervalId: number | null = null;
|
||||
|
||||
constructor(
|
||||
private plugin: DeepSeekPlugin,
|
||||
private tasks: ScheduledTaskConfig[]
|
||||
) {}
|
||||
|
||||
start(): void {
|
||||
// Check every 60 seconds
|
||||
this.intervalId = window.setInterval(() => void this.tick(), 60_000);
|
||||
this.plugin.registerInterval(this.intervalId);
|
||||
|
||||
// Immediate catch-up for on-startup tasks
|
||||
void this.tick();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.intervalId !== null) {
|
||||
window.clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async tick(): Promise<void> {
|
||||
const now = new Date();
|
||||
for (const task of this.tasks) {
|
||||
if (!task.enabled) continue;
|
||||
if (this.running.has(task.id)) continue;
|
||||
if (!this.calc.isDue(task.trigger, task.lastRunAt, now)) continue;
|
||||
|
||||
await this.runTask(task, now);
|
||||
}
|
||||
}
|
||||
|
||||
private async runTask(task: ScheduledTaskConfig, now: Date): Promise<void> {
|
||||
this.running.add(task.id);
|
||||
try {
|
||||
const result = await this.executeTaskKind(task);
|
||||
task.lastRunAt = now.toISOString();
|
||||
task.nextRunAt = this.calc.getNextRun(task.trigger, now).toISOString();
|
||||
|
||||
if (result.success) {
|
||||
new Notice(`⏰ ${task.name}:${result.message}`);
|
||||
}
|
||||
} catch (err) {
|
||||
new Notice(`⏰ ${task.name} 失败:${(err as Error).message}`);
|
||||
} finally {
|
||||
this.running.delete(task.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async executeTaskKind(task: ScheduledTaskConfig): Promise<ScheduledTaskResult> {
|
||||
const base: Omit<ScheduledTaskResult, "success" | "message"> = {
|
||||
taskId: task.id,
|
||||
ranAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
switch (task.kind) {
|
||||
case "knowledge-debt-scan":
|
||||
return this.runDebtScan(task, base);
|
||||
|
||||
case "baidu-backup":
|
||||
return this.runBaiduBackup(task, base);
|
||||
|
||||
case "question-graph-rebuild":
|
||||
case "reading-review":
|
||||
case "stale-draft-review":
|
||||
case "custom":
|
||||
// Placeholder — generate notice only
|
||||
return { ...base, success: true, message: "已完成扫描(详细实现待补充)" };
|
||||
}
|
||||
}
|
||||
|
||||
private async runDebtScan(
|
||||
task: ScheduledTaskConfig,
|
||||
base: Omit<ScheduledTaskResult, "success" | "message">
|
||||
): Promise<ScheduledTaskResult> {
|
||||
const index = this.plugin.knowledgeIndex;
|
||||
const emptyConcepts = index.getByType("concept").filter((e) => e.status === "empty" || e.status === "pending");
|
||||
const total = emptyConcepts.length;
|
||||
|
||||
if (task.safety === "notify-only") {
|
||||
return { ...base, success: true, message: `发现 ${total} 个空概念页` };
|
||||
}
|
||||
|
||||
// create-plan-only / auto-execute-low-risk → write report
|
||||
const reportContent = this.buildDebtReport(index);
|
||||
const plan = new PlanBuilder("每日知识债务扫描", "assistant")
|
||||
.createFile(`Knowledge/_Reports/${todayIso()}-知识债务.md`, reportContent)
|
||||
.build();
|
||||
|
||||
if (task.safety === "auto-execute-low-risk" && plan.riskLevel === "low") {
|
||||
await new PlanExecutor(this.plugin.app).execute(plan);
|
||||
return { ...base, success: true, message: `报告已生成(${total} 个空概念)` };
|
||||
}
|
||||
|
||||
// Just persist the plan draft (user confirms later)
|
||||
await new PlanExecutor(this.plugin.app).execute(plan);
|
||||
return { ...base, success: true, message: `报告已生成(${total} 个空概念)` };
|
||||
}
|
||||
|
||||
private async runBaiduBackup(
|
||||
task: ScheduledTaskConfig,
|
||||
base: Omit<ScheduledTaskResult, "success" | "message">
|
||||
): Promise<ScheduledTaskResult> {
|
||||
const cfg = this.plugin.settings.baiduSync;
|
||||
if (!cfg.enabled || !cfg.accessToken) {
|
||||
return { ...base, success: false, message: "百度同步未启用或未授权" };
|
||||
}
|
||||
|
||||
// Delegate to existing sync service
|
||||
const { BaiduSyncService } = await import("../../features/sync/BaiduSyncService");
|
||||
const service = new BaiduSyncService(this.plugin.app, cfg);
|
||||
const adapter = this.plugin.app.vault.adapter as unknown as { basePath?: string };
|
||||
const ok = await service.pushConfig(this.plugin.settings, adapter.basePath ?? "device");
|
||||
return { ...base, success: ok, message: ok ? "配置已备份" : "备份失败" };
|
||||
}
|
||||
|
||||
private buildDebtReport(index: KnowledgeIndexService): string {
|
||||
const empty = index.getByType("concept").filter((e) => e.status === "empty" || e.status === "pending");
|
||||
const lines = [
|
||||
`---\ntype: report\nreport_type: knowledge-debt\ncreated_at: ${todayIso()}\n---\n`,
|
||||
`# 知识债务报告 ${todayIso()}\n`,
|
||||
`## 空概念页 (${empty.length})\n`,
|
||||
...empty.slice(0, 30).map((e) => `- [[${e.basename}]]${e.domain ? ` [${e.domain}]` : ""}`),
|
||||
empty.length > 30 ? `\n...还有 ${empty.length - 30} 个\n` : "",
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
9
src/core/scheduler/index.ts
Normal file
9
src/core/scheduler/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export { ScheduledTaskRunner } from "./ScheduledTaskRunner";
|
||||
export { NextRunCalculator } from "./NextRunCalculator";
|
||||
export type {
|
||||
ScheduledTaskKind,
|
||||
ScheduleTrigger,
|
||||
ScheduledTaskSafety,
|
||||
ScheduledTaskConfig,
|
||||
ScheduledTaskResult,
|
||||
} from "./types";
|
||||
48
src/core/scheduler/types.ts
Normal file
48
src/core/scheduler/types.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Scheduled task types for IStart-Note-AI.
|
||||
*
|
||||
* Tasks run ONLY while Obsidian is open and the plugin is enabled.
|
||||
* If a scheduled run is missed, the runner offers catch-up on next launch.
|
||||
*/
|
||||
|
||||
export type ScheduledTaskKind =
|
||||
| "knowledge-debt-scan"
|
||||
| "baidu-backup"
|
||||
| "question-graph-rebuild"
|
||||
| "reading-review"
|
||||
| "stale-draft-review"
|
||||
| "custom";
|
||||
|
||||
export type ScheduleTrigger =
|
||||
| { type: "on-startup" }
|
||||
| { type: "interval"; minutes: number }
|
||||
| { type: "daily"; time: string } // "22:00" (HH:mm, local)
|
||||
| { type: "weekly"; weekday: number; time: string }; // weekday: 0=Sun
|
||||
|
||||
export type ScheduledTaskSafety =
|
||||
| "notify-only"
|
||||
| "create-plan-only"
|
||||
| "auto-execute-low-risk";
|
||||
|
||||
export interface ScheduledTaskConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
kind: ScheduledTaskKind;
|
||||
trigger: ScheduleTrigger;
|
||||
safety: ScheduledTaskSafety;
|
||||
lastRunAt?: string; // ISO
|
||||
nextRunAt?: string; // ISO
|
||||
scope?: {
|
||||
paths?: string[];
|
||||
types?: string[];
|
||||
domains?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ScheduledTaskResult {
|
||||
taskId: string;
|
||||
ranAt: string;
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ export class KnowledgeDebtModal extends Modal {
|
|||
});
|
||||
|
||||
// Reading chapters: files in "Reading" path with status != completed
|
||||
const unfinishedReadings = [...this.index["entries"].values()].filter((e) => {
|
||||
const unfinishedReadings = this.index.getAll().filter((e) => {
|
||||
return (
|
||||
e.path.includes("Reading/") &&
|
||||
e.type !== "domain-index" &&
|
||||
|
|
@ -153,7 +153,7 @@ export class KnowledgeDebtModal extends Modal {
|
|||
);
|
||||
});
|
||||
|
||||
const staleNotes = [...this.index["entries"].values()].filter((e) => {
|
||||
const staleNotes = this.index.getAll().filter((e) => {
|
||||
return (
|
||||
e.status === "draft" &&
|
||||
now - e.mtime > NINETY_DAYS
|
||||
|
|
|
|||
30
src/main.ts
30
src/main.ts
|
|
@ -28,11 +28,14 @@ import { SCHEMA_VERSION, todayIso } from "./core/schema";
|
|||
import { KnowledgeIndexService } from "./core/knowledge";
|
||||
import { PlanBuilder } from "./core/execution";
|
||||
import { PlanExecutor } from "./core/execution";
|
||||
import { ScheduledTaskRunner, ScheduledTaskConfig } from "./core/scheduler";
|
||||
|
||||
export default class DeepSeekPlugin extends Plugin {
|
||||
settings!: DeepSeekSettings;
|
||||
/** In-memory vault knowledge index, rebuilt on load, updated incrementally. */
|
||||
knowledgeIndex!: KnowledgeIndexService;
|
||||
/** Scheduled task runner — only active while Obsidian is open. */
|
||||
private scheduler: ScheduledTaskRunner | null = null;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
|
@ -41,6 +44,7 @@ export default class DeepSeekPlugin extends Plugin {
|
|||
this.knowledgeIndex = new KnowledgeIndexService(this.app);
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.knowledgeIndex.rebuild();
|
||||
this.startScheduler();
|
||||
});
|
||||
// Incremental updates
|
||||
this.registerEvent(
|
||||
|
|
@ -676,6 +680,32 @@ ${selection ? `用户当前选中的文字:\n${selection}\n` : ""}`;
|
|||
.map((f) => f.basename);
|
||||
}
|
||||
|
||||
// ── 定时任务 ─────────────────────────────────────────────────
|
||||
|
||||
private startScheduler(): void {
|
||||
const defaultTasks: ScheduledTaskConfig[] = [
|
||||
{
|
||||
id: "daily-debt-scan",
|
||||
name: "每日知识债务扫描",
|
||||
enabled: false, // user must opt-in via settings
|
||||
kind: "knowledge-debt-scan",
|
||||
trigger: { type: "daily", time: "22:00" },
|
||||
safety: "notify-only",
|
||||
},
|
||||
{
|
||||
id: "daily-baidu-backup",
|
||||
name: "每日百度备份",
|
||||
enabled: this.settings.baiduSync.enabled && this.settings.baiduSync.autoBackup,
|
||||
kind: "baidu-backup",
|
||||
trigger: { type: "daily", time: "23:00" },
|
||||
safety: "auto-execute-low-risk",
|
||||
},
|
||||
];
|
||||
|
||||
this.scheduler = new ScheduledTaskRunner(this, defaultTasks);
|
||||
this.scheduler.start();
|
||||
}
|
||||
|
||||
// ── 设置 ───────────────────────────────────────────────────
|
||||
|
||||
async loadSettings() {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||
import { App, Notice, PluginSettingTab, Setting } from "obsidian";
|
||||
import type DeepSeekPlugin from "../main";
|
||||
import type { DeepSeekSettings } from "../types";
|
||||
import { BaiduAuthModal } from "../features/sync/BaiduAuthModal";
|
||||
|
||||
type SettingsSection = "knowledge" | "execution" | "auxiliary";
|
||||
|
||||
export class DeepSeekSettingsTab extends PluginSettingTab {
|
||||
private activeSection: SettingsSection = "auxiliary"; // start with AI setup
|
||||
|
||||
constructor(app: App, private plugin: DeepSeekPlugin) {
|
||||
super(app, plugin);
|
||||
}
|
||||
|
|
@ -11,54 +15,190 @@ export class DeepSeekSettingsTab extends PluginSettingTab {
|
|||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
new Setting(containerEl).setName("DeepSeek Knowledge Graph 设置").setHeading();
|
||||
containerEl.addClass("istart-settings-root");
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("API key")
|
||||
.setDesc("DeepSeek API Key(在 platform.deepseek.com 获取)")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("sk-...")
|
||||
// ── Header ───────────────────────────────────────────────
|
||||
containerEl.createEl("h2", { text: "IStart-Note-AI" });
|
||||
containerEl.createEl("p", {
|
||||
text: "知识沉淀 · 执行计划 · 同步辅助",
|
||||
attr: { style: "color: var(--text-muted); margin-top: -8px; margin-bottom: 16px;" },
|
||||
});
|
||||
|
||||
// ── Layout ───────────────────────────────────────────────
|
||||
const layout = containerEl.createDiv({ cls: "istart-settings-layout" });
|
||||
const sidebar = layout.createDiv({ cls: "istart-settings-sidebar" });
|
||||
const content = layout.createDiv({ cls: "istart-settings-content" });
|
||||
|
||||
this.renderNav(sidebar);
|
||||
this.renderSection(content);
|
||||
}
|
||||
|
||||
private renderNav(container: HTMLElement): void {
|
||||
const sections: { id: SettingsSection; label: string; icon: string }[] = [
|
||||
{ id: "knowledge", label: "知识", icon: "📚" },
|
||||
{ id: "execution", label: "执行", icon: "⚡" },
|
||||
{ id: "auxiliary", label: "辅助", icon: "🔧" },
|
||||
];
|
||||
|
||||
for (const sec of sections) {
|
||||
const item = container.createDiv({
|
||||
cls: `istart-settings-nav-item${this.activeSection === sec.id ? " is-active" : ""}`,
|
||||
});
|
||||
item.setText(`${sec.icon} ${sec.label}`);
|
||||
item.addEventListener("click", () => {
|
||||
this.activeSection = sec.id;
|
||||
this.display();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private renderSection(container: HTMLElement): void {
|
||||
switch (this.activeSection) {
|
||||
case "knowledge":
|
||||
this.renderKnowledge(container);
|
||||
break;
|
||||
case "execution":
|
||||
this.renderExecution(container);
|
||||
break;
|
||||
case "auxiliary":
|
||||
this.renderAuxiliary(container);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// 知识
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
private renderKnowledge(el: HTMLElement): void {
|
||||
new Setting(el).setName("知识路径").setHeading();
|
||||
|
||||
new Setting(el)
|
||||
.setName("Q&A 笔记目录")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("Knowledge/Q&A").setValue(this.plugin.settings.savePath).onChange(async (v) => {
|
||||
this.plugin.settings.savePath = v.trim() || "Knowledge/Q&A";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("问题索引目录")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("Knowledge/Questions").setValue(this.plugin.settings.questionsIndexPath).onChange(async (v) => {
|
||||
this.plugin.settings.questionsIndexPath = v.trim() || "Knowledge/Questions";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("概念页目录")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("Knowledge/Concepts").setValue(this.plugin.settings.conceptsPath).onChange(async (v) => {
|
||||
this.plugin.settings.conceptsPath = v.trim() || "Knowledge/Concepts";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// ── 知识索引 ──────────────────────────────────────────────
|
||||
new Setting(el).setName("知识索引").setHeading();
|
||||
|
||||
const indexSize = this.plugin.knowledgeIndex?.size ?? 0;
|
||||
new Setting(el)
|
||||
.setName("索引状态")
|
||||
.setDesc(`已索引 ${indexSize} 篇笔记`)
|
||||
.addButton((btn) =>
|
||||
btn.setButtonText("重建索引").onClick(() => {
|
||||
this.plugin.knowledgeIndex.rebuild();
|
||||
new Notice(`✅ 索引已重建:${this.plugin.knowledgeIndex.size} 篇`);
|
||||
this.display();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("生成笔记后自动打开图谱")
|
||||
.addToggle((t) =>
|
||||
t.setValue(this.plugin.settings.autoOpenGraph).onChange(async (v) => {
|
||||
this.plugin.settings.autoOpenGraph = v;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// 执行
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
private renderExecution(el: HTMLElement): void {
|
||||
new Setting(el).setName("执行计划").setHeading();
|
||||
|
||||
el.createEl("p", {
|
||||
text: "执行模块当前为实验阶段。所有 AI 生成的写入操作都会先生成计划,需要你确认后再执行。回滚功能尚未实现。",
|
||||
attr: { style: "color: var(--text-muted); font-size: 13px; margin-bottom: 12px;" },
|
||||
});
|
||||
|
||||
new Setting(el)
|
||||
.setName("执行日志目录")
|
||||
.setDesc("每次执行计划后自动生成日志")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("Knowledge/_Executions").setValue("Knowledge/_Executions").setDisabled(true)
|
||||
);
|
||||
|
||||
new Setting(el).setName("定时任务").setHeading();
|
||||
el.createEl("p", {
|
||||
text: "定时任务即将推出(v2.1)。支持每日知识债务扫描、自动百度备份、每周问题图谱重建。",
|
||||
attr: { style: "color: var(--text-muted); font-size: 13px;" },
|
||||
});
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// 辅助
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
private renderAuxiliary(el: HTMLElement): void {
|
||||
// ── AI 服务 ───────────────────────────────────────────────
|
||||
new Setting(el).setName("AI 服务").setHeading();
|
||||
|
||||
new Setting(el)
|
||||
.setName("API Key")
|
||||
.setDesc("DeepSeek 或其他 OpenAI 兼容服务的密钥")
|
||||
.addText((t) => {
|
||||
t.setPlaceholder("sk-...")
|
||||
.setValue(this.plugin.settings.apiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiKey = value.trim();
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.apiKey = v.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
});
|
||||
t.inputEl.type = "password";
|
||||
return t;
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
new Setting(el)
|
||||
.setName("Base URL")
|
||||
.setDesc("API 地址,默认 https://api.deepseek.com")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("https://api.deepseek.com")
|
||||
.setValue(this.plugin.settings.baseUrl)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.baseUrl = value.trim() || "https://api.deepseek.com";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
.setDesc("Chat completions 端点根地址")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("https://api.deepseek.com").setValue(this.plugin.settings.baseUrl).onChange(async (v) => {
|
||||
this.plugin.settings.baseUrl = v.trim() || "https://api.deepseek.com";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
new Setting(el)
|
||||
.setName("模型")
|
||||
.setDesc("选择使用的 DeepSeek 模型")
|
||||
.addDropdown((drop) =>
|
||||
drop
|
||||
.addOption("deepseek-v4-flash", "deepseek-v4-flash(快速,推荐)")
|
||||
.addDropdown((d) =>
|
||||
d.addOption("deepseek-v4-flash", "deepseek-v4-flash(快速)")
|
||||
.addOption("deepseek-v4-pro", "deepseek-v4-pro(深度推理)")
|
||||
.setValue(this.plugin.settings.model)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.model = value as "deepseek-v4-flash" | "deepseek-v4-pro";
|
||||
.onChange(async (v: string) => {
|
||||
this.plugin.settings.model = v as "deepseek-v4-flash" | "deepseek-v4-pro";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
new Setting(el)
|
||||
.setName("输出风格")
|
||||
.setDesc("AI 生成内容的默认风格")
|
||||
.addDropdown((drop) =>
|
||||
drop
|
||||
.addOption("knowledge-base", "知识库(推荐)")
|
||||
.addDropdown((d) =>
|
||||
d.addOption("knowledge-base", "知识库(推荐)")
|
||||
.addOption("technical", "技术文档")
|
||||
.addOption("minimal", "极简")
|
||||
.addOption("product", "产品设计")
|
||||
|
|
@ -66,67 +206,16 @@ export class DeepSeekSettingsTab extends PluginSettingTab {
|
|||
.addOption("story", "世界观/叙事")
|
||||
.addOption("dashboard", "卡片化")
|
||||
.setValue(this.plugin.settings.outputStyle)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.outputStyle = value as DeepSeekSettings["outputStyle"];
|
||||
.onChange(async (v: string) => {
|
||||
this.plugin.settings.outputStyle = v as DeepSeekSettings["outputStyle"];
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("笔记保存路径")
|
||||
.setDesc("Q&A 笔记存储目录(相对于 Vault 根目录)")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Knowledge/Q&A")
|
||||
.setValue(this.plugin.settings.savePath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.savePath = value.trim() || "Knowledge/Q&A";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
// ── 百度网盘同步 ──────────────────────────────────────────
|
||||
new Setting(el).setName("百度网盘同步").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("问题索引路径")
|
||||
.setDesc("问题图谱索引页存储目录")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Knowledge/Questions")
|
||||
.setValue(this.plugin.settings.questionsIndexPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.questionsIndexPath = value.trim() || "Knowledge/Questions";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("概念页保存路径")
|
||||
.setDesc("概念页存储目录(相对于 Vault 根目录)")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Knowledge/Concepts")
|
||||
.setValue(this.plugin.settings.conceptsPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.conceptsPath = value.trim() || "Knowledge/Concepts";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("自动打开 Graph view")
|
||||
.setDesc("生成笔记后自动打开图谱视图")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.autoOpenGraph)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autoOpenGraph = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// ── 百度云同步 ──────────────────────────────────────────
|
||||
new Setting(containerEl).setName("百度网盘同步").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
new Setting(el)
|
||||
.setName("启用百度云同步")
|
||||
.addToggle((t) =>
|
||||
t.setValue(this.plugin.settings.baiduSync.enabled).onChange(async (v) => {
|
||||
|
|
@ -137,142 +226,100 @@ export class DeepSeekSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
if (this.plugin.settings.baiduSync.enabled) {
|
||||
new Setting(containerEl)
|
||||
.setName("App ID")
|
||||
.setDesc("百度开放平台应用的 App Key")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("your-app-id")
|
||||
.setValue(this.plugin.settings.baiduSync.appId)
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.baiduSync.appId = v.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
this.renderBaiduSyncSettings(el);
|
||||
}
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("App secret")
|
||||
.setDesc("百度开放平台应用的 Secret Key")
|
||||
.addText((text) => {
|
||||
text
|
||||
.setPlaceholder("your-app-secret")
|
||||
.setValue(this.plugin.settings.baiduSync.appSecret)
|
||||
.onChange((v) => {
|
||||
this.plugin.settings.baiduSync.appSecret = v.trim();
|
||||
void this.plugin.saveSettings();
|
||||
});
|
||||
text.inputEl.type = "password";
|
||||
return text;
|
||||
// ── 隐私与诊断 ───────────────────────────────────────────
|
||||
new Setting(el).setName("隐私与诊断").setHeading();
|
||||
el.createEl("p", {
|
||||
text: "AI 功能会把选中内容和部分上下文发送到所配置的 API 端点。同步功能会把笔记上传到你自己的百度网盘。完整说明见 PRIVACY.md。",
|
||||
attr: { style: "color: var(--text-muted); font-size: 13px;" },
|
||||
});
|
||||
}
|
||||
|
||||
private renderBaiduSyncSettings(el: HTMLElement): void {
|
||||
const cfg = this.plugin.settings.baiduSync;
|
||||
|
||||
new Setting(el)
|
||||
.setName("App ID")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("your-app-id").setValue(cfg.appId).onChange(async (v) => {
|
||||
cfg.appId = v.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("App Secret")
|
||||
.addText((t) => {
|
||||
t.setPlaceholder("your-app-secret").setValue(cfg.appSecret).onChange((v) => {
|
||||
cfg.appSecret = v.trim();
|
||||
void this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
const tokenStatus = this.plugin.settings.baiduSync.accessToken
|
||||
? `已授权(过期时间:${this.plugin.settings.baiduSync.tokenExpiresAt?.slice(0, 10) ?? "未知"})`
|
||||
: "未授权";
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("授权状态")
|
||||
.setDesc(tokenStatus)
|
||||
.addButton((btn) =>
|
||||
btn.setButtonText("重新授权").onClick(() => {
|
||||
new BaiduAuthModal(
|
||||
this.app,
|
||||
this.plugin.settings.baiduSync,
|
||||
(accessToken, refreshToken, expiresAt) => {
|
||||
this.plugin.settings.baiduSync.accessToken = accessToken;
|
||||
this.plugin.settings.baiduSync.refreshToken = refreshToken;
|
||||
this.plugin.settings.baiduSync.tokenExpiresAt = expiresAt;
|
||||
void this.plugin.saveSettings().then(() => this.display());
|
||||
}
|
||||
).open();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("远端备份路径")
|
||||
.setDesc("百度网盘中的备份根目录")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("/apps/istart-note-ai")
|
||||
.setValue(this.plugin.settings.baiduSync.remotePath)
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.baiduSync.remotePath = v.trim() || "/apps/istart-note-ai";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("自动备份")
|
||||
.setDesc("每次生成笔记后自动备份到百度云")
|
||||
.addToggle((t) =>
|
||||
t.setValue(this.plugin.settings.baiduSync.autoBackup).onChange(async (v) => {
|
||||
this.plugin.settings.baiduSync.autoBackup = v;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("备份插件本身")
|
||||
.setDesc("备份时包含插件文件(main.js、配置等),方便换设备恢复")
|
||||
.addToggle((t) =>
|
||||
t.setValue(this.plugin.settings.baiduSync.backupPlugin).onChange(async (v) => {
|
||||
this.plugin.settings.baiduSync.backupPlugin = v;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("忽略规则")
|
||||
.setDesc("正则表达式,匹配的文件路径将被跳过")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("node_modules|.git")
|
||||
.setValue(this.plugin.settings.baiduSync.ignorePattern)
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.baiduSync.ignorePattern = v.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("单文件大小限制(MB)")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("100")
|
||||
.setValue(String(this.plugin.settings.baiduSync.fileSizeLimitMB))
|
||||
.onChange(async (v) => {
|
||||
const n = parseInt(v);
|
||||
if (!isNaN(n) && n > 0) {
|
||||
this.plugin.settings.baiduSync.fileSizeLimitMB = n;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 配置同步
|
||||
new Setting(containerEl).setName("配置同步").setHeading();
|
||||
containerEl.createEl("p", {
|
||||
text: "将路径、模型等偏好设置同步到百度云,在多台设备间共享(不含 API Key 和 Token 等凭证)。",
|
||||
cls: "istart-settings-config-hint",
|
||||
t.inputEl.type = "password";
|
||||
return t;
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("推送配置到百度云")
|
||||
.setDesc("将当前设置上传,其他设备可拉取同步")
|
||||
.addButton((btn) =>
|
||||
btn.setButtonText("推送").onClick(async () => {
|
||||
await this.plugin.pushConfig();
|
||||
})
|
||||
);
|
||||
const tokenStatus = cfg.accessToken
|
||||
? `已授权(过期:${cfg.tokenExpiresAt?.slice(0, 10) ?? "未知"})`
|
||||
: "未授权";
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("从百度云拉取配置")
|
||||
.setDesc("拉取最新配置并应用(不覆盖凭证)")
|
||||
.addButton((btn) =>
|
||||
btn.setButtonText("拉取").onClick(async () => {
|
||||
await this.plugin.pullConfig();
|
||||
this.display();
|
||||
})
|
||||
);
|
||||
}
|
||||
new Setting(el)
|
||||
.setName("授权状态")
|
||||
.setDesc(tokenStatus)
|
||||
.addButton((btn) =>
|
||||
btn.setButtonText("授权").onClick(() => {
|
||||
new BaiduAuthModal(this.app, cfg, (accessToken, refreshToken, expiresAt) => {
|
||||
cfg.accessToken = accessToken;
|
||||
cfg.refreshToken = refreshToken;
|
||||
cfg.tokenExpiresAt = expiresAt;
|
||||
void this.plugin.saveSettings().then(() => this.display());
|
||||
}).open();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("远端路径")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("/apps/istart-note-ai").setValue(cfg.remotePath).onChange(async (v) => {
|
||||
cfg.remotePath = v.trim() || "/apps/istart-note-ai";
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("自动备份")
|
||||
.setDesc("生成笔记后自动备份")
|
||||
.addToggle((t) => t.setValue(cfg.autoBackup).onChange(async (v) => { cfg.autoBackup = v; await this.plugin.saveSettings(); }));
|
||||
|
||||
new Setting(el)
|
||||
.setName("备份插件本身")
|
||||
.addToggle((t) => t.setValue(cfg.backupPlugin).onChange(async (v) => { cfg.backupPlugin = v; await this.plugin.saveSettings(); }));
|
||||
|
||||
new Setting(el)
|
||||
.setName("忽略规则")
|
||||
.setDesc("正则表达式")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("node_modules|.git").setValue(cfg.ignorePattern).onChange(async (v) => {
|
||||
cfg.ignorePattern = v.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("单文件大小限制(MB)")
|
||||
.addText((t) =>
|
||||
t.setPlaceholder("100").setValue(String(cfg.fileSizeLimitMB)).onChange(async (v) => {
|
||||
const n = parseInt(v);
|
||||
if (!isNaN(n) && n > 0) { cfg.fileSizeLimitMB = n; await this.plugin.saveSettings(); }
|
||||
})
|
||||
);
|
||||
|
||||
// 配置同步
|
||||
new Setting(el).setName("跨设备配置同步").setDesc("不含凭证").setHeading();
|
||||
|
||||
new Setting(el)
|
||||
.addButton((btn) => btn.setButtonText("推送配置").onClick(() => void this.plugin.pushConfig()))
|
||||
.addButton((btn) => btn.setButtonText("拉取配置").onClick(async () => { await this.plugin.pullConfig(); this.display(); }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
57
styles.css
57
styles.css
|
|
@ -1,5 +1,62 @@
|
|||
/* IStart-Note-AI Plugin Styles */
|
||||
|
||||
/* ── Settings Page Layout ────────────────────────── */
|
||||
|
||||
.istart-settings-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 160px minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.istart-settings-sidebar {
|
||||
border-right: 1px solid var(--background-modifier-border);
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.istart-settings-content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.istart-settings-nav-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
color: var(--text-normal);
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.istart-settings-nav-item:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.istart-settings-nav-item.is-active {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--text-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.istart-settings-layout {
|
||||
display: block;
|
||||
}
|
||||
.istart-settings-sidebar {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.istart-settings-nav-item {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Baidu Sync View ─────────────────────────────── */
|
||||
|
||||
.istart-sync-root {
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@
|
|||
"1.8.1": "1.7.2",
|
||||
"1.8.2": "1.7.2",
|
||||
"1.8.3": "1.7.2",
|
||||
"1.9.0": "1.7.2"
|
||||
"1.9.0": "1.7.2",
|
||||
"2.0.0": "1.7.2"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue