diff --git a/manifest.json b/manifest.json index 362b7ac..181980b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "istart-note-ai", "name": "IStart-Note-AI", - "version": "1.7.3", + "version": "1.7.4", "minAppVersion": "1.4.0", "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", diff --git a/package.json b/package.json index d9cfbe7..e3af6b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "istart-note-ai", - "version": "1.7.3", + "version": "1.7.4", "description": "IStart-Note-AI: DeepSeek-powered knowledge graph plugin for Obsidian", "main": "main.js", "scripts": { diff --git a/src/actions/definitions.ts b/src/actions/definitions.ts new file mode 100644 index 0000000..75b0233 --- /dev/null +++ b/src/actions/definitions.ts @@ -0,0 +1,215 @@ +import { Notice } from "obsidian"; +import { ActionDef } from "./types"; + +/** + * 所有插件动作的定义。 + * 新增功能只需在此数组中添加一条。 + */ +export const ALL_ACTIONS: ActionDef[] = [ + // ── 通用 ────────────────────────────────────────────────── + { + id: "ask-deepseek", + label: "提问", + icon: "message-circle", + description: "向 AI 提问并生成知识笔记", + group: "general", + when: { always: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => ctx.plugin.openQuestionModal(), + }, + { + id: "new-reading-project", + label: "新建阅读项目", + icon: "book-open", + description: "输入书名,生成阅读地图", + group: "general", + when: { always: true }, + showIn: ["panel"], + run: (ctx) => ctx.plugin.openNewReadingProject(), + }, + { + id: "baidu-sync", + label: "百度云同步", + icon: "cloud", + group: "general", + when: { always: true }, + showIn: ["panel"], + run: (ctx) => ctx.plugin.openBaiduSyncModal(), + }, + + // ── 选中文字 ────────────────────────────────────────────── + { + id: "context-qa", + label: "基于选中内容提问", + icon: "help-circle", + group: "selection", + when: { hasSelection: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => { + const path = ctx.activeFile?.path ?? ""; + ctx.plugin.openContextQAModal(ctx.selection, path); + }, + }, + { + id: "generate-diagram", + label: "生成图表 / 公式", + icon: "bar-chart-2", + group: "selection", + when: { hasSelection: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => { + if (!ctx.editor) return; + ctx.plugin.openDiagramGenerator(ctx.selection, ctx.fileContent.slice(0, 800), ctx.editor); + }, + }, + { + id: "expand-selection", + label: "扩写选中内容", + icon: "expand", + group: "selection", + when: { hasSelection: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => { + if (!ctx.editor) return; + void ctx.plugin.runExpand(ctx.selection, ctx.fileContent.slice(0, 1500), ctx.editor); + }, + }, + + // ── 编辑 ────────────────────────────────────────────────── + { + id: "smart-complete", + label: "智能补全", + icon: "sparkles", + description: "自动判断:补全/扩写/续写", + group: "edit", + when: { always: true }, + showIn: ["panel", "editor-menu", "file-menu"], + run: (ctx) => { + if (ctx.editor) { + void ctx.plugin.runSmartComplete(ctx.editor); + } else if (ctx.targetFile) { + // file-menu: 先打开文件再执行 + void (async () => { + const leaf = ctx.app.workspace.getLeaf(false); + await leaf.openFile(ctx.targetFile!); + setTimeout(() => { + const ed = ctx.app.workspace.activeEditor?.editor; + if (ed) void ctx.plugin.runSmartComplete(ed); + }, 200); + })(); + } + }, + }, + { + id: "continue-writing", + label: "续写", + icon: "pencil", + description: "从光标位置继续写", + group: "edit", + when: { noSelection: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => { + if (!ctx.editor) return; + const cursor = ctx.editor.getCursor(); + const before = ctx.editor.getRange({ line: 0, ch: 0 }, cursor); + void ctx.plugin.runContinue(before, ctx.editor); + }, + }, + { + id: "append-section", + label: "补充当前章节", + icon: "plus-circle", + group: "edit", + when: { inSection: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => { + if (!ctx.editor || !ctx.activeFile || !ctx.sectionName) return; + void ctx.plugin.runSectionAppend(ctx.activeFile, ctx.sectionName, ctx.fileContent); + }, + }, + + // ── 概念页 ──────────────────────────────────────────────── + { + id: "complete-concept", + label: "补全概念页", + icon: "brain", + group: "concept", + when: { fileType: ["concept"], filePath: "Concepts/" }, + showIn: ["panel", "editor-menu", "file-menu"], + run: (ctx) => { void ctx.plugin.completeCurrentConcept(); }, + }, + { + id: "scan-empty-concepts", + label: "扫描空概念页", + icon: "search", + group: "concept", + when: { fileType: ["concept"], filePath: "Concepts/" }, + showIn: ["panel"], + run: (ctx) => { void ctx.plugin.scanAndBatchComplete(); }, + }, + + // ── 阅读 ────────────────────────────────────────────────── + { + id: "resume-reading", + label: "补全阅读项目", + icon: "refresh-cw", + description: "补全缺失章节的预设问题", + group: "reading", + when: { fileType: ["reading-project"] }, + showIn: ["panel", "editor-menu", "file-menu"], + run: (ctx) => { void ctx.plugin.resumeReadingProject(); }, + }, + { + id: "chapter-summary", + label: "生成章节总结", + icon: "file-text", + group: "reading", + when: { fileType: ["reading-note"] }, + showIn: ["panel", "editor-menu", "file-menu"], + run: (ctx) => { + const editor = ctx.editor ?? ctx.app.workspace.activeEditor?.editor; + if (editor) void ctx.plugin.runChapterSummary(editor); + }, + }, + { + id: "feynman-test", + label: "费曼检验", + icon: "help-circle", + description: "检验理解程度", + group: "reading", + when: { fileType: ["reading-note"] }, + showIn: ["panel", "editor-menu", "file-menu"], + run: (ctx) => { + const editor = ctx.editor ?? ctx.app.workspace.activeEditor?.editor; + if (editor) void ctx.plugin.runFeynmanTest(editor); + }, + }, + + // ── 文档工具 ────────────────────────────────────────────── + { + id: "analyze-document", + label: "分析文档缺失", + icon: "search", + description: "AI 分析并建议补充内容", + group: "document", + when: { always: true }, + showIn: ["panel", "editor-menu"], + run: (ctx) => { + if (!ctx.editor) return; + void ctx.plugin.runDocumentAnalysis(ctx.fileContent, ctx.editor); + }, + }, + { + id: "smart-diagram", + label: "智能生成图表", + icon: "bar-chart-2", + description: "AI 自动判断最合适的图表类型", + group: "document", + when: { hasSelection: true }, + showIn: ["panel"], + run: (ctx) => { + if (!ctx.editor) return; + void ctx.plugin.runDiagramGeneration(ctx.selection, "auto", ctx.fileContent.slice(0, 800), ctx.editor); + }, + }, +]; diff --git a/src/actions/registry.ts b/src/actions/registry.ts new file mode 100644 index 0000000..1739aff --- /dev/null +++ b/src/actions/registry.ts @@ -0,0 +1,195 @@ +import { Notice, TFile } from "obsidian"; +import type DeepSeekPlugin from "../main"; +import { ActionDef, ActionContext, ActionEntry, ActionGroup, GROUP_TITLES, GROUP_ORDER } from "./types"; +import { CommandPanelModal } from "../features/command-panel/CommandPanelModal"; +import type { PanelGroup, PanelAction } from "../features/command-panel/CommandPanelModal"; +import { SectionAppender } from "../ai/SectionAppender"; + +/** + * 注册所有 actions 到插件的各个入口(命令、右键菜单、面板) + */ +export function registerAllActions(plugin: DeepSeekPlugin, actions: ActionDef[]) { + // 1. 为每个 action 注册命令 + // 需要编辑器的 action 用 editorCallback(移动端工具栏可用) + for (const action of actions) { + const needsEditor = action.showIn.includes("editor-menu") || action.when.hasSelection || action.when.inSection || action.when.noSelection; + + if (needsEditor) { + plugin.addCommand({ + id: action.id, + name: action.label, + editorCallback: (editor) => { + const ctx = buildContext(plugin, null); + ctx.editor = editor; + ctx.selection = editor.getSelection().trim(); + ctx.fileContent = editor.getValue(); + const cursor = editor.getCursor(); + const appender = new SectionAppender(plugin.app, plugin.settings); + ctx.sectionName = appender.getSectionAtCursor(ctx.fileContent, cursor.line); + if (evaluateWhen(action.when, ctx)) { + action.run(ctx); + } else { + new Notice(`当前上下文不支持此操作`); + } + }, + }); + } else { + plugin.addCommand({ + id: action.id, + name: action.label, + callback: () => { + const ctx = buildContext(plugin, null); + if (evaluateWhen(action.when, ctx)) { + action.run(ctx); + } else { + new Notice(`当前上下文不支持此操作`); + } + }, + }); + } + } + + // 2. 注册 editor-menu + plugin.registerEvent( + plugin.app.workspace.on("editor-menu", (menu, editor) => { + const ctx = buildContext(plugin, null); + ctx.editor = editor; + ctx.selection = editor.getSelection().trim(); + ctx.fileContent = editor.getValue(); + + // 重新计算 sectionName + const cursor = editor.getCursor(); + const appender = new SectionAppender(plugin.app, plugin.settings); + ctx.sectionName = appender.getSectionAtCursor(ctx.fileContent, cursor.line); + + const visible = actions.filter( + (a) => a.showIn.includes("editor-menu") && evaluateWhen(a.when, ctx) + ); + + for (const action of visible) { + menu.addItem((item) => { + item + .setTitle(`IStart-Note-AI: ${action.label}`) + .setIcon(action.icon) + .onClick(() => action.run(ctx)); + }); + } + }) + ); + + // 3. 注册 file-menu + plugin.registerEvent( + plugin.app.workspace.on("file-menu", (menu, file) => { + if (!(file instanceof TFile) || file.extension !== "md") return; + + const fileMeta = plugin.app.metadataCache.getFileCache(file); + const fileType = fileMeta?.frontmatter?.type as string | undefined; + + const ctx = buildContext(plugin, file); + ctx.fileType = fileType; + ctx.filePath = file.path; + ctx.targetFile = file; + + const visible = actions.filter( + (a) => a.showIn.includes("file-menu") && evaluateWhen(a.when, ctx) + ); + + for (const action of visible) { + menu.addItem((item) => { + item + .setTitle(`IStart-Note-AI: ${action.label}`) + .setIcon(action.icon) + .onClick(() => action.run(ctx)); + }); + } + }) + ); + + // 4. 注册面板打开命令 + plugin.addCommand({ + id: "open-panel", + name: "Open command panel", + callback: () => openPanel(plugin, actions), + }); + + // 5. Ribbon icon 打开面板 + plugin.addRibbonIcon("brain", "IStart-Note-AI", () => { + openPanel(plugin, actions); + }); +} + +/** 打开统一面板 */ +function openPanel(plugin: DeepSeekPlugin, actions: ActionDef[]) { + const editor = plugin.app.workspace.activeEditor?.editor ?? null; + const ctx = buildContext(plugin, null); + if (editor) { + ctx.editor = editor; + ctx.selection = editor.getSelection().trim(); + ctx.fileContent = editor.getValue(); + const cursor = editor.getCursor(); + const appender = new SectionAppender(plugin.app, plugin.settings); + ctx.sectionName = appender.getSectionAtCursor(ctx.fileContent, cursor.line); + } + + // 按 group 分组,过滤可见 actions + 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) + ); + if (groupActions.length === 0) continue; + + const panelActions: PanelAction[] = groupActions.map((a) => ({ + id: a.id, + icon: a.icon, + label: a.label, + description: a.description, + callback: () => a.run(ctx), + })); + + groups.push({ title: GROUP_TITLES[groupId], actions: panelActions }); + } + + new CommandPanelModal(plugin.app, groups).open(); +} + +/** 构建当前上下文 */ +function buildContext(plugin: DeepSeekPlugin, targetFile: TFile | null): ActionContext { + const activeFile = plugin.app.workspace.getActiveFile(); + const file = targetFile ?? activeFile; + const fileMeta = file ? plugin.app.metadataCache.getFileCache(file) : null; + + return { + plugin, + app: plugin.app, + editor: null, + activeFile, + selection: "", + fileContent: "", + fileType: fileMeta?.frontmatter?.type as string | undefined, + filePath: file?.path ?? "", + sectionName: null, + targetFile, + }; +} + +/** 评估可见性条件 */ +function evaluateWhen(when: ActionDef["when"], ctx: ActionContext): boolean { + if (when.always) return true; + + if (when.hasSelection && !ctx.selection) return false; + if (when.noSelection && ctx.selection) return false; + + if (when.fileType) { + const match = when.fileType.some((t) => ctx.fileType === t) || + (when.filePath && ctx.filePath.includes(when.filePath)); + if (!match) return false; + } else if (when.filePath) { + if (!ctx.filePath.includes(when.filePath)) return false; + } + + if (when.inSection && !ctx.sectionName) return false; + + return true; +} diff --git a/src/actions/types.ts b/src/actions/types.ts new file mode 100644 index 0000000..b9aabe3 --- /dev/null +++ b/src/actions/types.ts @@ -0,0 +1,61 @@ +import type { App, Editor, TFile } from "obsidian"; +import type DeepSeekPlugin from "../main"; + +/** 动作执行时的上下文 */ +export interface ActionContext { + plugin: DeepSeekPlugin; + 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 专用:右键的目标文件(可能不是当前打开的文件) + targetFile: TFile | null; +} + +/** 可见性条件 */ +export interface ActionWhen { + always?: boolean; // 始终可见 + hasSelection?: boolean; // 需要有选中文字 + noSelection?: boolean; // 需要没有选中文字 + fileType?: string[]; // frontmatter type 匹配其一 + filePath?: string; // 文件路径包含此字符串 + inSection?: boolean; // 光标在某个 ## section 内 +} + +/** 动作出现的入口 */ +export type ActionEntry = "panel" | "editor-menu" | "file-menu"; + +/** 面板分组 */ +export type ActionGroup = "general" | "selection" | "edit" | "concept" | "reading" | "sync" | "document"; + +/** 动作定义 */ +export interface ActionDef { + id: string; + label: string; + icon: string; + description?: string; + group: ActionGroup; + when: ActionWhen; + showIn: ActionEntry[]; + run: (ctx: ActionContext) => void; +} + +/** 分组标题映射 */ +export const GROUP_TITLES: Record = { + general: "通用", + selection: "选中文字", + edit: "编辑", + concept: "概念页", + reading: "阅读", + sync: "同步", + document: "文档工具", +}; + +/** 分组排序 */ +export const GROUP_ORDER: ActionGroup[] = [ + "general", "selection", "edit", "concept", "reading", "sync", "document", +]; diff --git a/src/features/sync/BaiduSyncView.ts b/src/features/sync/BaiduSyncView.ts index e5fdc54..e466ef9 100644 --- a/src/features/sync/BaiduSyncView.ts +++ b/src/features/sync/BaiduSyncView.ts @@ -64,6 +64,7 @@ export class BaiduSyncView extends ItemView { this.makeBtn(btnRow, "⬆ 强制备份", "default", () => { void this.forceBackup(); }); this.makeBtn(btnRow, "⬇ 强制更新", "default", () => { void this.forceUpdate(); }); this.makeBtn(btnRow, "⇄ 双向同步", "cta", () => { void this.runSync(); }); + this.makeBtn(btnRow, "⚠️ 强制覆盖", "default", () => { void this.forceOverwrite(); }); // 上次扫描时间 if (this.lastScanTime) { @@ -251,6 +252,54 @@ export class BaiduSyncView extends ItemView { await this.scan(); } + async forceOverwrite() { + const cfg = this.plugin.settings.baiduSync; + const service = new BaiduSyncService(this.app, cfg); + const tokenOk = await service.ensureValidToken(); + if (!tokenOk) { new Notice("Token 已过期,请重新授权"); return; } + + // 二次确认 + const confirmed = await new Promise((resolve) => { + const modal = new (class extends (require("obsidian") as { Modal: new (app: import("obsidian").App) => import("obsidian").Modal }).Modal { + onOpen() { + this.titleEl.setText("⚠️ 确认强制覆盖"); + this.contentEl.createEl("p", { + text: "此操作将删除本地所有同步文件,然后从云端完整恢复。不可撤销!", + cls: "istart-sync-modal-warning", + }); + const { Setting } = require("obsidian") as typeof import("obsidian"); + new Setting(this.contentEl) + .addButton((btn: import("obsidian").ButtonComponent) => btn.setButtonText("确认覆盖").setWarning().onClick(() => { this.close(); resolve(true); })) + .addButton((btn: import("obsidian").ButtonComponent) => btn.setButtonText("取消").onClick(() => { this.close(); resolve(false); })); + } + onClose() { this.contentEl.empty(); resolve(false); } + })(this.app); + modal.open(); + }); + + if (!confirmed) return; + + const notice = new Notice("⏳ 强制覆盖:清理本地...", 0); + + // 删除本地文件 + const files = this.app.vault.getFiles().filter( + (f) => !f.path.split("/").some((p) => p.startsWith(".")) + ); + for (const f of files) { + try { await this.app.vault.delete(f); } catch { /* ignore */ } + } + + // 从云端恢复 + notice.setMessage("⏳ 强制覆盖:从云端恢复..."); + const result = await service.restore("", true, (c, t, file) => { + notice.setMessage(`⏳ 恢复中 (${c}/${t}):${file.split("/").pop()}`); + }); + notice.hide(); + + new Notice(`✅ 强制覆盖完成:恢复 ${result.downloaded} 个文件`); + await this.scan(); + } + private async uploadOne(path: string) { const cfg = this.plugin.settings.baiduSync; const abstract = this.app.vault.getAbstractFileByPath(path); diff --git a/src/main.ts b/src/main.ts index c8a4010..e619a7f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,832 +26,72 @@ import { DocumentAnalysisModal, SmartPreviewModal } from "./features/smart-compl import { ReadingPlanner } from "./ai/ReadingPlanner"; import { NewReadingModal, FeynmanModal } from "./features/reading/ReadingModal"; import { ReadingProjectManager } from "./features/reading/ReadingProjectManager"; -import { CommandPanelModal, buildPanelGroups } from "./features/command-panel/CommandPanelModal"; +import { registerAllActions } from "./actions/registry"; +import { ALL_ACTIONS } from "./actions/definitions"; export default class DeepSeekPlugin extends Plugin { settings: DeepSeekSettings; async onload() { await this.loadSettings(); - - this.addRibbonIcon("brain", "IStart-Note-AI", () => { - this.openCommandPanel(); - }); - - this.addRibbonIcon("cloud", "Baidu cloud sync status", () => { - void this.activateSyncView(); - }); - this.registerView(SYNC_VIEW_TYPE, (leaf) => new BaiduSyncView(leaf, this)); - - this.addCommand({ - id: "ask-deepseek", - name: "Ask DeepSeek and generate a knowledge note", - callback: () => this.openQuestionModal(), - }); - - // 命令:打开命令面板 - this.addCommand({ - id: "open-panel", - name: "Open command panel", - callback: () => this.openCommandPanel(), - }); - - this.addCommand({ - id: "complete-current-concept", - name: "Complete current concept page", - callback: () => { void this.completeCurrentConcept(); }, - }); - - this.addCommand({ - id: "scan-empty-concepts", - name: "Scan empty concept pages", - callback: () => { void this.scanAndBatchComplete(); }, - }); - - this.addCommand({ - id: "context-qa", - name: "Ask based on selection", - editorCallback: (editor) => { - const selection = editor.getSelection().trim(); - if (!selection) { - new Notice("请先选中一段文字"); - return; - } - const activeFile = this.app.workspace.getActiveFile(); - this.openContextQAModal(selection, activeFile?.path ?? ""); - }, - }); - - this.addCommand({ - id: "append-current-section", - name: "Append content to current section", - editorCallback: (editor) => { - const cursor = editor.getCursor(); - const content = editor.getValue(); - const appender = new SectionAppender(this.app, this.settings); - const sectionName = appender.getSectionAtCursor(content, cursor.line); - if (!sectionName) { - new Notice("请将光标置于某个章节(## 标题)内"); - return; - } - const activeFile = this.app.workspace.getActiveFile(); - if (!activeFile) return; - void this.runSectionAppend(activeFile, sectionName, content); - }, - }); - - this.addCommand({ - id: "open-question-index", - name: "Open question index", - callback: () => { void this.openQuestionIndex(); }, - }); - - this.addCommand({ - id: "baidu-sync", - name: "Baidu Netdisk sync / backup", - callback: () => this.openBaiduSyncModal(), - }); - - this.addCommand({ - id: "baidu-sync-view", - name: "Open Baidu cloud sync status panel", - callback: () => { void this.activateSyncView(); }, - }); - - this.addCommand({ - id: "baidu-auth", - name: "Baidu Netdisk re-authorize", - callback: () => this.openBaiduAuthModal(), - }); - - // 命令:生成图表/公式(选中文字) - this.addCommand({ - id: "generate-diagram", - name: "Generate diagram or formula from selection", - editorCallback: (editor) => { - const selection = editor.getSelection().trim(); - if (!selection) { - new Notice("请先选中一段文字作为图表/公式的描述"); - return; - } - const context = editor.getValue().slice(0, 800); - this.openDiagramGenerator(selection, context, editor); - }, - }); - - // 命令:智能生成(自动判断类型) - this.addCommand({ - id: "smart-diagram", - name: "Smart generate (auto-detect type)", - editorCallback: (editor) => { - const selection = editor.getSelection().trim(); - if (!selection) { - new Notice("请先选中一段文字"); - return; - } - const context = editor.getValue().slice(0, 800); - void this.runDiagramGeneration(selection, "auto", context, editor); - }, - }); - - // 命令:智能补全(自动判断场景) - this.addCommand({ - id: "smart-complete", - name: "Smart complete (auto-detect context)", - editorCallback: (editor) => { - void this.runSmartComplete(editor); - }, - }); - - // 命令:扩写选中内容 - this.addCommand({ - id: "expand-selection", - name: "Expand selected text", - editorCallback: (editor) => { - const selection = editor.getSelection().trim(); - if (!selection) { - new Notice("请先选中要扩写的文字"); - return; - } - const context = editor.getValue().slice(0, 1500); - void this.runExpand(selection, context, editor); - }, - }); - - // 命令:续写 - this.addCommand({ - id: "continue-writing", - name: "Continue writing from cursor", - editorCallback: (editor) => { - const cursor = editor.getCursor(); - const beforeCursor = editor.getRange({ line: 0, ch: 0 }, cursor); - if (!beforeCursor.trim()) { - new Notice("光标前没有内容可续写"); - return; - } - void this.runContinue(beforeCursor, editor); - }, - }); - - // 命令:分析文档缺失 - this.addCommand({ - id: "analyze-document", - name: "Analyze document and suggest completions", - editorCallback: (editor) => { - const content = editor.getValue(); - if (!content.trim()) { - new Notice("文档为空"); - return; - } - void this.runDocumentAnalysis(content, editor); - }, - }); - - // 命令:新建阅读项目 - this.addCommand({ - id: "new-reading-project", - name: "New reading project (book study)", - callback: () => this.openNewReadingProject(), - }); - - // 命令:生成章节总结 - this.addCommand({ - id: "chapter-summary", - name: "Generate chapter summary", - editorCallback: (editor) => { - void this.runChapterSummary(editor); - }, - }); - - // 命令:费曼检验 - this.addCommand({ - id: "feynman-test", - name: "Feynman test (check understanding)", - editorCallback: (editor) => { - void this.runFeynmanTest(editor); - }, - }); - - // 命令:补全阅读项目(断点续传) - this.addCommand({ - id: "resume-reading-project", - name: "Resume reading project (complete missing chapters)", - callback: () => { void this.resumeReadingProject(); }, - }); - + this.addRibbonIcon("cloud", "Baidu cloud sync status", () => { void this.activateSyncView(); }); this.addSettingTab(new DeepSeekSettingsTab(this.app, this)); - - this.registerEvent( - this.app.workspace.on("file-menu", (menu, file) => { - if (!(file instanceof TFile) || file.extension !== "md") return; - - const fileMeta = this.app.metadataCache.getFileCache(file); - const fileType = fileMeta?.frontmatter?.type as string | undefined; - - // 通用:智能补全(对任何 md 文件可用) - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Smart complete") - .setIcon("sparkles") - .onClick(() => { - void (async () => { - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - // 等文件打开后执行智能补全 - setTimeout(() => { - const editor = this.app.workspace.activeEditor?.editor; - if (editor) void this.runSmartComplete(editor); - }, 200); - })(); - }); - }); - - // 概念页:补全概念 - if (fileType === "concept" || file.path.includes("Concepts/")) { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Complete this concept page") - .setIcon("brain") - .onClick(() => { - void (async () => { - const manager = new ConceptPageManager(this.app, this.settings); - const info = await manager.analyzeFile(file); - if (!info) { - new Notice("该文件不是概念页"); - return; - } - new DepthSelectModal(this.app, info.conceptName, (depth) => { - void this.runConceptCompletion(info.file, info.conceptName, depth, { - sourceQuestion: info.sourceQuestion, - sourceAnswer: info.sourceAnswer, - }); - }).open(); - })(); - }); - }); - } - - // 阅读项目索引页:补全缺失章节 - if (fileType === "reading-project") { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Resume reading project") - .setIcon("refresh-cw") - .onClick(() => { void this.resumeReadingProject(); }); - }); - } - - // 阅读章节笔记:生成总结 / 费曼检验 - if (fileType === "reading-note") { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Generate chapter summary") - .setIcon("file-text") - .onClick(() => { - void (async () => { - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - setTimeout(() => { - const editor = this.app.workspace.activeEditor?.editor; - if (editor) void this.runChapterSummary(editor); - }, 200); - })(); - }); - }); - - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Feynman test") - .setIcon("help-circle") - .onClick(() => { - void (async () => { - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - setTimeout(() => { - const editor = this.app.workspace.activeEditor?.editor; - if (editor) void this.runFeynmanTest(editor); - }, 200); - })(); - }); - }); - } - }) - ); - - this.registerEvent( - this.app.workspace.on("editor-menu", (menu, editor) => { - const selection = editor.getSelection().trim(); - - if (selection) { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Ask based on selection") - .setIcon("message-circle") - .onClick(() => { - const activeFile = this.app.workspace.getActiveFile(); - this.openContextQAModal(selection, activeFile?.path ?? ""); - }); - }); - - // 图表/公式生成入口 - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Generate diagram / formula") - .setIcon("bar-chart-2") - .onClick(() => { - const context = editor.getValue().slice(0, 800); - this.openDiagramGenerator(selection, context, editor); - }); - }); - - // 扩写选中内容 - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Expand selection") - .setIcon("expand") - .onClick(() => { - const context = editor.getValue().slice(0, 1500); - void this.runExpand(selection, context, editor); - }); - }); - } - - const cursor = editor.getCursor(); - const fullContent = editor.getValue(); - const appender = new SectionAppender(this.app, this.settings); - const sectionAtCursor = appender.getSectionAtCursor(fullContent, cursor.line); - if (sectionAtCursor) { - menu.addItem((item) => { - item - .setTitle(`IStart-Note-AI: Append to "${sectionAtCursor}"`) - .setIcon("plus-circle") - .onClick(() => { - const activeFile = this.app.workspace.getActiveFile(); - if (!activeFile) return; - void this.runSectionAppend(activeFile, sectionAtCursor, fullContent); - }); - }); - } - - const linkMatch = selection.match(/^\[\[(.+?)(?:\|.+?)?\]\]$/) || - selection.match(/^(.+)$/); - const conceptName = linkMatch?.[1]; - - if (conceptName) { - menu.addItem((item) => { - item - .setTitle(`IStart-Note-AI: Complete concept "${conceptName}"`) - .setIcon("brain") - .onClick(() => { - void (async () => { - const manager = new ConceptPageManager(this.app, this.settings); - const conceptsPath = this.settings.conceptsPath || "Knowledge/Concepts"; - - // 在所有子目录中查找概念文件 - let conceptFile: TFile | null = null; - const allFiles = this.app.vault.getMarkdownFiles(); - const found = allFiles.find( - (f) => f.path.startsWith(conceptsPath) && f.basename === conceptName - ); - if (found) { - conceptFile = found; - } - - if (!conceptFile) { - const writer = new VaultWriter(this.app, this.settings); - await writer.ensureConceptNote(conceptName); - // 重新查找(现在在 _未分类/ 下) - const created = this.app.vault.getMarkdownFiles().find( - (f) => f.path.startsWith(conceptsPath) && f.basename === conceptName - ); - if (created) conceptFile = created; - else { - // 直接用路径查找 - const uncatPath = `${conceptsPath}/_未分类/${conceptName}.md`; - const uncatFile = this.app.vault.getAbstractFileByPath(uncatPath); - if (uncatFile instanceof TFile) conceptFile = uncatFile; - } - } - - if (!conceptFile) { - new Notice(`无法找到或创建概念页:${conceptName}`); - return; - } - - const targetFile = conceptFile; - const info = await manager.analyzeFile(targetFile); - new DepthSelectModal(this.app, conceptName, (depth) => { - void this.runConceptCompletion(targetFile, conceptName, depth, { - sourceQuestion: info?.sourceQuestion, - sourceAnswer: info?.sourceAnswer, - }); - }).open(); - })(); - }); - }); - } - - // 根据文件类型显示对应操作 - const activeFile = this.app.workspace.getActiveFile(); - const activeMeta = activeFile ? this.app.metadataCache.getFileCache(activeFile) : null; - const activeType = activeMeta?.frontmatter?.type as string | undefined; - const isConceptPage = activeType === "concept" || (activeFile?.path.includes("Concepts/") ?? false); - const isReadingNote = activeType === "reading-note"; - const isReadingProject = activeType === "reading-project"; - - if (isConceptPage) { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Complete current concept page") - .setIcon("brain") - .onClick(() => { void this.completeCurrentConcept(); }); - }); - } - - if (isReadingNote) { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Generate chapter summary") - .setIcon("file-text") - .onClick(() => { void this.runChapterSummary(editor); }); - }); - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Feynman test") - .setIcon("help-circle") - .onClick(() => { void this.runFeynmanTest(editor); }); - }); - } - - if (isReadingProject) { - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Resume reading project") - .setIcon("refresh-cw") - .onClick(() => { void this.resumeReadingProject(); }); - }); - } - - // 智能补全(始终可用) - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Smart complete") - .setIcon("sparkles") - .onClick(() => { void this.runSmartComplete(editor); }); - }); - - // 分析文档(始终可用) - menu.addItem((item) => { - item - .setTitle("IStart-Note-AI: Analyze and suggest") - .setIcon("search") - .onClick(() => { - const content = editor.getValue(); - void this.runDocumentAnalysis(content, editor); - }); - }); - }) - ); + registerAllActions(this, ALL_ACTIONS); } - private openCommandPanel() { - const editor = this.app.workspace.activeEditor?.editor; - const activeFile = this.app.workspace.getActiveFile(); - const selection = editor?.getSelection().trim() ?? ""; - const hasSelection = selection.length > 0; + // ── 公共方法(供 Action 定义调用) ───────────────────────── - // 判断当前文件类型 - const meta = activeFile ? this.app.metadataCache.getFileCache(activeFile) : null; - const fm = meta?.frontmatter; - const isConceptPage = fm?.type === "concept" || (activeFile?.path.includes("Concepts/") ?? false); - const isReadingNote = fm?.type === "reading-note"; - - // 判断光标是否在 section 内 - let isInSection = false; - let sectionName: string | null = null; - if (editor) { - const cursor = editor.getCursor(); - const content = editor.getValue(); - const appender = new SectionAppender(this.app, this.settings); - sectionName = appender.getSectionAtCursor(content, cursor.line); - isInSection = sectionName !== null; - } - - const groups = buildPanelGroups({ - hasSelection, - selection, - isConceptPage, - isReadingNote, - isInSection, - sectionName, - activeFile, - onAsk: () => this.openQuestionModal(), - onContextQA: () => { - if (editor && hasSelection) { - this.openContextQAModal(selection, activeFile?.path ?? ""); - } - }, - onNewReading: () => this.openNewReadingProject(), - onSmartComplete: () => { if (editor) void this.runSmartComplete(editor); }, - onDiagram: () => { - if (editor && hasSelection) { - const context = editor.getValue().slice(0, 800); - this.openDiagramGenerator(selection, context, editor); - } - }, - onExpand: () => { - if (editor && hasSelection) { - const context = editor.getValue().slice(0, 1500); - void this.runExpand(selection, context, editor); - } - }, - onContinue: () => { - if (editor) { - const cursor = editor.getCursor(); - const before = editor.getRange({ line: 0, ch: 0 }, cursor); - void this.runContinue(before, editor); - } - }, - onCompleteConcept: () => { void this.completeCurrentConcept(); }, - onScanConcepts: () => { void this.scanAndBatchComplete(); }, - onChapterSummary: () => { if (editor) void this.runChapterSummary(editor); }, - onFeynmanTest: () => { if (editor) void this.runFeynmanTest(editor); }, - onAnalyzeDoc: () => { - if (editor) { - const content = editor.getValue(); - void this.runDocumentAnalysis(content, editor); - } - }, - onSectionAppend: () => { - if (editor && activeFile && sectionName) { - const content = editor.getValue(); - void this.runSectionAppend(activeFile, sectionName, content); - } - }, - }); - - new CommandPanelModal(this.app, groups).open(); - } - - private openContextQAModal(selectedText: string, sourceNotePath: string) { - new ContextQAModal(this.app, selectedText, (question) => { - void this.processContextQA(question, selectedText, sourceNotePath); - }).open(); - } - - private async processContextQA(question: string, context: string, sourceNotePath: string) { - const notice = new Notice("⏳ 基于上下文思考中...", 0); - - try { - const client = new ContextQAClient(this.settings); - const graphManager = new QuestionGraphManager(this.app, this.settings); - - let surroundingContext: string | undefined; - if (sourceNotePath) { - const sourceFile = this.app.vault.getAbstractFileByPath(sourceNotePath); - if (sourceFile instanceof TFile) { - const fullContent = await this.app.vault.read(sourceFile); - surroundingContext = fullContent.slice(0, 500); - } - } - - const [response, history] = await Promise.all([ - client.ask({ question, context, sourceNote: sourceNotePath, surroundingContext }), - Promise.resolve(graphManager.getQuestionHistory()), - ]); - - notice.hide(); - - const classifier = new QuestionClassifier(this.settings); - const classifyNotice = new Notice("🔍 分析问题关系...", 0); - const classification = await classifier.classify(question, history); - classifyNotice.hide(); - - new QuestionClassifyModal(this.app, question, classification, (confirmed) => { - void (async () => { - const writeNotice = new Notice("✍️ 写入笔记...", 0); - try { - const writer = new VaultWriter(this.app, this.settings); - const file = await writer.writeContextQANote( - { question, context, sourceNote: sourceNotePath, surroundingContext }, - response - ); - - await graphManager.attachClassification(file, question, confirmed, response.concepts); - await graphManager.appendRecommendations(file, confirmed); - await graphManager.updateQuestionIndex(question, confirmed, file.path); - - writeNotice.hide(); - new Notice(`✅ 上下文笔记已生成:${file.name}`); - - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - - void this.triggerAutoBackup(file.path); - } catch (err) { - writeNotice.hide(); - new Notice(`❌ 写入失败:${(err as Error).message}`); - console.error("[IStart-Note-AI]", err); - } - })(); - }).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 错误:${(err as Error).message}`); - console.error("[IStart-Note-AI]", err); - } - } - - private openQuestionModal() { + openQuestionModal() { new QuestionModal(this.app, (question) => { void this.processQuestion(question); }).open(); } - private async processQuestion(question: string) { - const notice = new Notice("⏳ DeepSeek 思考中...", 0); - - try { - const client = new DeepSeekClient(this.settings); - const graphManager = new QuestionGraphManager(this.app, this.settings); - - const [response, history] = await Promise.all([ - client.ask(question), - Promise.resolve(graphManager.getQuestionHistory()), - ]); - - notice.hide(); - - const classifier = new QuestionClassifier(this.settings); - const classifyNotice = new Notice("🔍 分析问题关系...", 0); - const classification = await classifier.classify(question, history); - classifyNotice.hide(); - - new QuestionClassifyModal(this.app, question, classification, (confirmed) => { - void (async () => { - const writeNotice = new Notice("✍️ 写入笔记...", 0); - try { - const writer = new VaultWriter(this.app, this.settings); - const file = await writer.writeQANote(question, response); - - await graphManager.attachClassification(file, question, confirmed, response.concepts); - await graphManager.appendRecommendations(file, confirmed); - await graphManager.updateQuestionIndex(question, confirmed, file.path); - - writeNotice.hide(); - new Notice(`✅ 笔记已生成:${file.name}`); - - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - - if (this.settings.autoOpenGraph) { - const appWithCommands = this.app as unknown as { commands: { executeCommandById: (id: string) => void } }; - appWithCommands.commands.executeCommandById("graph:open"); - } - - void this.triggerAutoBackup(file.path); - } catch (err) { - writeNotice.hide(); - new Notice(`❌ 写入失败:${(err as Error).message}`); - console.error("[DeepSeek Plugin]", err); - } - })(); - }).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 错误:${(err as Error).message}`); - console.error("[DeepSeek Plugin]", err); - } + openContextQAModal(selectedText: string, sourceNotePath: string) { + new ContextQAModal(this.app, selectedText, (question) => { + void this.processContextQA(question, selectedText, sourceNotePath); + }).open(); } - private async openQuestionIndex() { - const indexFolder = normalizePath(this.settings.questionsIndexPath); - const indexPath = normalizePath(`${indexFolder}/问题索引.md`); - let file = this.app.vault.getAbstractFileByPath(indexPath); - if (!file || !(file instanceof TFile)) { - try { await this.app.vault.createFolder(indexFolder); } catch { /* exists */ } - file = await this.app.vault.create(indexPath, "# 问题索引\n\n## 核心问题\n\n## 深化问题\n\n## 扩展问题\n"); - } - if (file instanceof TFile) { - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - } + openNewReadingProject() { + new NewReadingModal(this.app, (bookInfo, toc) => { + void this.createReadingProject(bookInfo, toc); + }).open(); } - private async completeCurrentConcept() { + openBaiduSyncModal() { + if (!this.settings.baiduSync.enabled) { new Notice("请先在设置中启用百度云同步"); return; } + new BaiduSyncModal(this.app, this.settings.baiduSync, async (accessToken, expiresAt) => { + this.settings.baiduSync.accessToken = accessToken; + this.settings.baiduSync.tokenExpiresAt = expiresAt; + await this.saveSettings(); + }).open(); + } + + openDiagramGenerator(selection: string, context: string, editor: import("obsidian").Editor) { + new DiagramTypeModal(this.app, (type) => { + void this.runDiagramGeneration(selection, type, context, editor); + }).open(); + } + + async completeCurrentConcept() { const manager = new ConceptPageManager(this.app, this.settings); const info = await manager.analyzeCurrentFile(); - - if (!info) { - new Notice("当前文件不是概念页,请打开 Knowledge/Concepts 下的概念文件"); - return; - } - + if (!info) { new Notice("当前文件不是概念页"); return; } new DepthSelectModal(this.app, info.conceptName, (depth) => { void this.runConceptCompletion(info.file, info.conceptName, depth, { - sourceQuestion: info.sourceQuestion, - sourceAnswer: info.sourceAnswer, + sourceQuestion: info.sourceQuestion, sourceAnswer: info.sourceAnswer, }); }).open(); } - private async runConceptCompletion( - file: TFile, - conceptName: string, - depth: CompletionDepth, - context: { sourceQuestion?: string; sourceAnswer?: string } - ) { - const notice = new Notice(`⏳ 正在补全概念:${conceptName}...`, 0); - - try { - const completer = new ConceptCompleter(this.settings); - const result = await completer.complete(conceptName, depth, context); - notice.hide(); - - const manager = new ConceptPageManager(this.app, this.settings); - const previewMd = manager.buildPreviewMarkdown(result, depth); - - new PreviewModal( - this.app, - conceptName, - previewMd, - () => { - void (async () => { - await manager.writeCompletion(file, result, depth); - new Notice(`✅ 概念页已补全:${conceptName}`); - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(file); - })(); - }, - () => { - void this.runConceptCompletion(file, conceptName, depth, context); - } - ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 补全失败:${(err as Error).message}`); - console.error("[DeepSeek Plugin]", err); - } - } - - private runSectionAppend(file: TFile, sectionName: string, content: string) { - const appender = new SectionAppender(this.app, this.settings); - const section = appender.extractSection(content, sectionName); - const existingItems = section?.existing - .split("\n") - .filter((l) => l.trim().startsWith("-")) - .length ?? 0; - - const meta = this.app.metadataCache.getFileCache(file); - const conceptName = (meta?.frontmatter?.name as string) || file.basename; - - new SectionAppendModal(this.app, sectionName, existingItems, (count) => { - void this.generateAndPreviewSection(file, conceptName, sectionName, section?.existing ?? "", count); - }).open(); - } - - private async generateAndPreviewSection( - file: TFile, - conceptName: string, - sectionName: string, - existingContent: string, - count: number - ) { - const notice = new Notice(`⏳ 生成"${sectionName}"补充内容...`, 0); - try { - const appender = new SectionAppender(this.app, this.settings); - const result = await appender.generate(conceptName, sectionName, existingContent, count); - notice.hide(); - - new SectionPreviewModal( - this.app, - sectionName, - result.items, - () => { - void (async () => { - await appender.appendToSection(file, sectionName, result.items); - new Notice(`✅ 已追加 ${result.items.length} 条内容到"${sectionName}"`); - })(); - }, - () => { - void this.generateAndPreviewSection(file, conceptName, sectionName, existingContent, count); - } - ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 生成失败:${(err as Error).message}`); - } - } - - private async scanAndBatchComplete() { + async scanAndBatchComplete() { const notice = new Notice("🔍 扫描空概念页中...", 0); const manager = new ConceptPageManager(this.app, this.settings); const empties = await manager.scanEmptyConcepts(); notice.hide(); - const items = empties.map((e) => ({ name: e.conceptName, path: e.file.path })); - new BatchScanModal(this.app, items, (selectedPaths, depth) => { void (async () => { let done = 0; @@ -860,69 +100,277 @@ export default class DeepSeekPlugin extends Plugin { if (!abstract || !(abstract instanceof TFile)) continue; const info = await manager.analyzeFile(abstract); if (!info) continue; - const n = new Notice(`⏳ 补全中 (${++done}/${selectedPaths.length}):${info.conceptName}`, 0); try { const completer = new ConceptCompleter(this.settings); - const result = await completer.complete(info.conceptName, depth, { - sourceQuestion: info.sourceQuestion, - sourceAnswer: info.sourceAnswer, - }); + const result = await completer.complete(info.conceptName, depth, { sourceQuestion: info.sourceQuestion, sourceAnswer: info.sourceAnswer }); await manager.writeCompletion(abstract, result, depth); n.hide(); - } catch (err) { - n.hide(); - new Notice(`❌ ${info.conceptName} 补全失败:${(err as Error).message}`); - } + } catch (err) { n.hide(); new Notice(`❌ ${info.conceptName} 补全失败:${(err as Error).message}`); } } new Notice(`✅ 批量补全完成,共 ${done} 个概念页`); })(); }).open(); } - private async activateSyncView() { - const { workspace } = this.app; - let leaf = workspace.getLeavesOfType(SYNC_VIEW_TYPE)[0]; - if (!leaf) { - leaf = workspace.getRightLeaf(false) ?? workspace.getLeaf(true); - await leaf.setViewState({ type: SYNC_VIEW_TYPE, active: true }); - } - await workspace.revealLeaf(leaf); + async resumeReadingProject() { + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile) { new Notice("请先打开阅读项目的索引页"); return; } + const meta = this.app.metadataCache.getFileCache(activeFile); + if (meta?.frontmatter?.type !== "reading-project") { new Notice("当前文件不是阅读项目索引页"); return; } + const notice = new Notice("⏳ 补全缺失章节...", 0); + try { + const manager = new ReadingProjectManager(this.app, this.settings); + const count = await manager.resumeProject(activeFile, (c, t, ch) => { notice.setMessage(`⏳ 补全 (${c}/${t}):${ch}`); }); + notice.hide(); + new Notice(count === 0 ? "✅ 所有章节已完整" : `✅ 已补全 ${count} 个章节`); + } catch (err) { notice.hide(); new Notice(`❌ 补全失败:${(err as Error).message}`); } } - private openBaiduSyncModal() { - if (!this.settings.baiduSync.enabled) { - new Notice("请先在设置中启用百度云同步"); - return; + async runChapterSummary(editor: import("obsidian").Editor) { + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile) { new Notice("请先打开章节笔记"); return; } + const content = editor.getValue(); + const fm = this.app.metadataCache.getFileCache(activeFile)?.frontmatter; + if (fm?.type !== "reading-note") { new Notice("当前文件不是阅读章节笔记"); return; } + const book = (fm.book as string) || "未知"; + const chapter = `第${fm.chapter}章:${fm.title}`; + const questionsMatch = content.match(/## 读前问题\n([\s\S]*?)(?=\n## )/); + const questions = questionsMatch ? questionsMatch[1].split("\n").filter((l) => l.trim().startsWith("- ")).map((l) => l.replace(/^- \[.\]\s*/, "").trim()) : []; + const notice = new Notice("⏳ 生成章节总结...", 0); + try { + const planner = new ReadingPlanner(this.settings); + const result = await planner.summarizeChapter(book, chapter, content, questions); + notice.hide(); + const manager = new ReadingProjectManager(this.app, this.settings); + await manager.writeChapterSummary(activeFile, result); + new Notice("✅ 章节总结已生成"); + const leaf = this.app.workspace.getLeaf(false); + await leaf.openFile(activeFile); + } catch (err) { notice.hide(); new Notice(`❌ 生成失败:${(err as Error).message}`); } + } + + async runFeynmanTest(editor: import("obsidian").Editor) { + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile) { new Notice("请先打开章节笔记"); return; } + const content = editor.getValue(); + const fm = this.app.metadataCache.getFileCache(activeFile)?.frontmatter; + if (fm?.type !== "reading-note") { new Notice("当前文件不是阅读章节笔记"); return; } + const book = (fm.book as string) || "未知"; + const chapter = `第${fm.chapter}章:${fm.title}`; + const conceptsMatch = content.match(/## 关联概念\n([\s\S]*?)(?=\n## |$)/); + const concepts = conceptsMatch ? conceptsMatch[1].match(/\[\[(.+?)\]\]/g)?.map((m) => m.replace(/\[\[|\]\]/g, "")) ?? [] : []; + const notice = new Notice("⏳ 生成检验问题...", 0); + try { + const planner = new ReadingPlanner(this.settings); + const questions = await planner.feynmanTest(book, chapter, concepts, content); + notice.hide(); + new FeynmanModal(this.app, chapter, questions).open(); + } catch (err) { notice.hide(); new Notice(`❌ 生成失败:${(err as Error).message}`); } + } + + async runSmartComplete(editor: import("obsidian").Editor) { + const selection = editor.getSelection().trim(); + if (selection) { await this.runExpand(selection, editor.getValue().slice(0, 1500), editor); return; } + const cursor = editor.getCursor(); + const content = editor.getValue(); + const lines = content.split("\n"); + let sectionName: string | null = null; + let sectionStartLine = -1; + for (let i = cursor.line; i >= 0; i--) { + const match = lines[i]?.match(/^##\s+(.+)/); + if (match) { sectionName = match[1].trim(); sectionStartLine = i; break; } } - new BaiduSyncModal(this.app, this.settings.baiduSync, async (accessToken, expiresAt) => { - this.settings.baiduSync.accessToken = accessToken; - this.settings.baiduSync.tokenExpiresAt = expiresAt; - await this.saveSettings(); + if (sectionName && sectionStartLine >= 0) { + let sectionEmpty = true; + for (let i = sectionStartLine + 1; i < lines.length; i++) { + if (/^##\s/.test(lines[i])) break; + if (lines[i].trim().length > 0) { sectionEmpty = false; break; } + } + if (sectionEmpty) { + const title = this.app.workspace.getActiveFile()?.basename ?? "未知"; + await this.runSectionComplete(title, sectionName, content, sectionStartLine, editor); + return; + } + } + const beforeCursor = editor.getRange({ line: 0, ch: 0 }, cursor); + await this.runContinue(beforeCursor, editor); + } + + async runExpand(selection: string, context: string, editor: import("obsidian").Editor) { + const notice = new Notice("⏳ 扩写中...", 0); + try { + const completer = new SmartCompleter(this.settings); + const result = await completer.expand(selection, context); + notice.hide(); + new SmartPreviewModal(this.app, "扩写预览", result.content, () => { editor.replaceSelection(result.content); new Notice("✅ 已扩写"); }, () => { void this.runExpand(selection, context, editor); }).open(); + } catch (err) { notice.hide(); new Notice(`❌ 扩写失败:${(err as Error).message}`); } + } + + async runContinue(beforeCursor: string, editor: import("obsidian").Editor) { + const notice = new Notice("⏳ 续写中...", 0); + try { + const completer = new SmartCompleter(this.settings); + const result = await completer.continueWriting(beforeCursor); + notice.hide(); + new SmartPreviewModal(this.app, "续写预览", result.content, () => { editor.replaceRange("\n" + result.content, editor.getCursor()); new Notice("✅ 已续写"); }, () => { void this.runContinue(beforeCursor, editor); }).open(); + } catch (err) { notice.hide(); new Notice(`❌ 续写失败:${(err as Error).message}`); } + } + + async runSectionAppend(file: TFile, sectionName: string, content: string) { + const appender = new SectionAppender(this.app, this.settings); + const section = appender.extractSection(content, sectionName); + const existingItems = section?.existing.split("\n").filter((l) => l.trim().startsWith("-")).length ?? 0; + const meta = this.app.metadataCache.getFileCache(file); + const conceptName = (meta?.frontmatter?.name as string) || file.basename; + new SectionAppendModal(this.app, sectionName, existingItems, (count) => { + void this.generateAndPreviewSection(file, conceptName, sectionName, section?.existing ?? "", count); }).open(); } - private openBaiduAuthModal() { - if (!this.settings.baiduSync.enabled) { - new Notice("请先在设置中启用百度云同步"); - return; - } - new BaiduAuthModal(this.app, this.settings.baiduSync, (accessToken, refreshToken, expiresAt) => { - void (async () => { - this.settings.baiduSync.accessToken = accessToken; - this.settings.baiduSync.refreshToken = refreshToken; - this.settings.baiduSync.tokenExpiresAt = expiresAt; - await this.saveSettings(); - })(); - }).open(); + async runDocumentAnalysis(content: string, editor: import("obsidian").Editor) { + const notice = new Notice("⏳ 分析文档中...", 0); + try { + const completer = new SmartCompleter(this.settings); + const suggestions = await completer.analyzeDocument(content); + notice.hide(); + new DocumentAnalysisModal(this.app, suggestions, (selected) => { + const parts = selected.map((s) => `## ${s.section}\n${s.content}`); + const insertText = "\n\n" + parts.join("\n\n") + "\n"; + const lastLine = editor.lastLine(); + editor.replaceRange(insertText, { line: lastLine, ch: editor.getLine(lastLine).length }); + new Notice(`✅ 已插入 ${selected.length} 处补充内容`); + }).open(); + } catch (err) { notice.hide(); new Notice(`❌ 分析失败:${(err as Error).message}`); } } - // ── 阅读项目 ───────────────────────────────────────────── + async runDiagramGeneration(selection: string, type: DiagramType, context: string, editor: import("obsidian").Editor) { + const notice = new Notice(`⏳ 生成图表中...`, 0); + try { + const generator = new DiagramGenerator(this.settings); + const result = await generator.generate(selection, type, context); + notice.hide(); + const formatted = generator.formatForInsert(result); + new DiagramPreviewModal(this.app, result, formatted, + () => { const cursor = editor.getCursor("to"); editor.replaceRange(`\n${formatted}\n`, { line: cursor.line + 1, ch: 0 }); new Notice(`✅ 已插入${result.typeName}`); }, + () => { void this.runDiagramGeneration(selection, type, context, editor); }, + (instruction) => { void this.runDiagramRefine(result.code, instruction, editor); } + ).open(); + } catch (err) { notice.hide(); new Notice(`❌ 生成失败:${(err as Error).message}`); } + } - private openNewReadingProject() { - new NewReadingModal(this.app, (bookInfo, toc) => { - void this.createReadingProject(bookInfo, toc); - }).open(); + // ── 私有方法 ─────────────────────────────────────────────── + + private async processContextQA(question: string, context: string, sourceNotePath: string) { + const notice = new Notice("⏳ 基于上下文思考中...", 0); + try { + const client = new ContextQAClient(this.settings); + const graphManager = new QuestionGraphManager(this.app, this.settings); + let surroundingContext: string | undefined; + if (sourceNotePath) { + const sourceFile = this.app.vault.getAbstractFileByPath(sourceNotePath); + if (sourceFile instanceof TFile) { surroundingContext = (await this.app.vault.read(sourceFile)).slice(0, 500); } + } + const [response, history] = await Promise.all([client.ask({ question, context, sourceNote: sourceNotePath, surroundingContext }), Promise.resolve(graphManager.getQuestionHistory())]); + notice.hide(); + const classifier = new QuestionClassifier(this.settings); + const classifyNotice = new Notice("🔍 分析问题关系...", 0); + const classification = await classifier.classify(question, history); + classifyNotice.hide(); + new QuestionClassifyModal(this.app, question, classification, (confirmed) => { + void (async () => { + const writeNotice = new Notice("✍️ 写入笔记...", 0); + try { + const writer = new VaultWriter(this.app, this.settings); + const file = await writer.writeContextQANote({ question, context, sourceNote: sourceNotePath, surroundingContext }, response); + await graphManager.attachClassification(file, question, confirmed, response.concepts); + await graphManager.appendRecommendations(file, confirmed); + await graphManager.updateQuestionIndex(question, confirmed, file.path); + writeNotice.hide(); + new Notice(`✅ 上下文笔记已生成:${file.name}`); + const leaf = this.app.workspace.getLeaf(false); + await leaf.openFile(file); + void this.triggerAutoBackup(file.path); + } catch (err) { writeNotice.hide(); new Notice(`❌ 写入失败:${(err as Error).message}`); } + })(); + }).open(); + } catch (err) { notice.hide(); new Notice(`❌ 错误:${(err as Error).message}`); } + } + + private async processQuestion(question: string) { + const notice = new Notice("⏳ DeepSeek 思考中...", 0); + try { + const client = new DeepSeekClient(this.settings); + const graphManager = new QuestionGraphManager(this.app, this.settings); + const [response, history] = await Promise.all([client.ask(question), Promise.resolve(graphManager.getQuestionHistory())]); + notice.hide(); + const classifier = new QuestionClassifier(this.settings); + const classifyNotice = new Notice("🔍 分析问题关系...", 0); + const classification = await classifier.classify(question, history); + classifyNotice.hide(); + new QuestionClassifyModal(this.app, question, classification, (confirmed) => { + void (async () => { + const writeNotice = new Notice("✍️ 写入笔记...", 0); + try { + const writer = new VaultWriter(this.app, this.settings); + const file = await writer.writeQANote(question, response); + await graphManager.attachClassification(file, question, confirmed, response.concepts); + await graphManager.appendRecommendations(file, confirmed); + await graphManager.updateQuestionIndex(question, confirmed, file.path); + writeNotice.hide(); + new Notice(`✅ 笔记已生成:${file.name}`); + const leaf = this.app.workspace.getLeaf(false); + await leaf.openFile(file); + if (this.settings.autoOpenGraph) { + const appCmd = this.app as unknown as { commands: { executeCommandById: (id: string) => void } }; + appCmd.commands.executeCommandById("graph:open"); + } + void this.triggerAutoBackup(file.path); + } catch (err) { writeNotice.hide(); new Notice(`❌ 写入失败:${(err as Error).message}`); } + })(); + }).open(); + } catch (err) { notice.hide(); new Notice(`❌ 错误:${(err as Error).message}`); } + } + + private async runConceptCompletion(file: TFile, conceptName: string, depth: CompletionDepth, context: { sourceQuestion?: string; sourceAnswer?: string }) { + const notice = new Notice(`⏳ 正在补全概念:${conceptName}...`, 0); + try { + const completer = new ConceptCompleter(this.settings); + const result = await completer.complete(conceptName, depth, context); + notice.hide(); + const manager = new ConceptPageManager(this.app, this.settings); + const previewMd = manager.buildPreviewMarkdown(result, depth); + new PreviewModal(this.app, conceptName, previewMd, + () => { void (async () => { await manager.writeCompletion(file, result, depth); new Notice(`✅ 概念页已补全:${conceptName}`); const leaf = this.app.workspace.getLeaf(false); await leaf.openFile(file); })(); }, + () => { void this.runConceptCompletion(file, conceptName, depth, context); } + ).open(); + } catch (err) { notice.hide(); new Notice(`❌ 补全失败:${(err as Error).message}`); } + } + + private async generateAndPreviewSection(file: TFile, conceptName: string, sectionName: string, existingContent: string, count: number) { + const notice = new Notice(`⏳ 生成"${sectionName}"补充内容...`, 0); + try { + const appender = new SectionAppender(this.app, this.settings); + const result = await appender.generate(conceptName, sectionName, existingContent, count); + notice.hide(); + new SectionPreviewModal(this.app, sectionName, result.items, + () => { void (async () => { await appender.appendToSection(file, sectionName, result.items); new Notice(`✅ 已追加 ${result.items.length} 条`); })(); }, + () => { void this.generateAndPreviewSection(file, conceptName, sectionName, existingContent, count); } + ).open(); + } catch (err) { notice.hide(); new Notice(`❌ 生成失败:${(err as Error).message}`); } + } + + private async runSectionComplete(title: string, sectionName: string, fileContent: string, sectionStartLine: number, editor: import("obsidian").Editor) { + const notice = new Notice(`⏳ 补全"${sectionName}"...`, 0); + try { + const completer = new SmartCompleter(this.settings); + const result = await completer.completeSection(title, sectionName, fileContent); + notice.hide(); + new SmartPreviewModal(this.app, `补全"${sectionName}"`, result.content, + () => { editor.replaceRange(result.content + "\n\n", { line: sectionStartLine + 1, ch: 0 }); new Notice(`✅ 已补全"${sectionName}"`); }, + () => { void this.runSectionComplete(title, sectionName, fileContent, sectionStartLine, editor); } + ).open(); + } catch (err) { notice.hide(); new Notice(`❌ 补全失败:${(err as Error).message}`); } } private async createReadingProject(bookInfo: string, toc?: string) { @@ -931,426 +379,74 @@ export default class DeepSeekPlugin extends Plugin { const planner = new ReadingPlanner(this.settings); const plan = await planner.planSkeleton(bookInfo, toc); notice.setMessage(`✍️ 创建项目结构(${plan.chapters.length} 章)...`); - const manager = new ReadingProjectManager(this.app, this.settings); - const indexFile = await manager.createProject(plan, (current, total, chapter) => { - notice.setMessage(`⏳ 生成预设问题 (${current}/${total}):${chapter}`); - }); + const indexFile = await manager.createProject(plan, (c, t, ch) => { notice.setMessage(`⏳ 生成预设问题 (${c}/${t}):${ch}`); }); notice.hide(); - new Notice(`✅ 阅读项目已创建:${plan.bookTitle}(${plan.chapters.length} 章)`); const leaf = this.app.workspace.getLeaf(false); await leaf.openFile(indexFile); - } catch (err) { - notice.hide(); - new Notice(`❌ 创建失败:${(err as Error).message}`); - console.error("[IStart-Note-AI]", err); - } + } catch (err) { notice.hide(); new Notice(`❌ 创建失败:${(err as Error).message}`); } } - private async resumeReadingProject() { - const activeFile = this.app.workspace.getActiveFile(); - if (!activeFile) { new Notice("请先打开阅读项目的索引页"); return; } - - const meta = this.app.metadataCache.getFileCache(activeFile); - if (meta?.frontmatter?.type !== "reading-project") { - new Notice("当前文件不是阅读项目索引页"); - return; - } - - const notice = new Notice("⏳ 补全缺失章节...", 0); - try { - const manager = new ReadingProjectManager(this.app, this.settings); - const count = await manager.resumeProject(activeFile, (current, total, chapter) => { - notice.setMessage(`⏳ 补全 (${current}/${total}):${chapter}`); - }); - notice.hide(); - - if (count === 0) { - new Notice("✅ 所有章节已完整,无需补全"); - } else { - new Notice(`✅ 已补全 ${count} 个章节的预设问题`); - } - } catch (err) { - notice.hide(); - new Notice(`❌ 补全失败:${(err as Error).message}`); - } - } - - private async runChapterSummary(editor: import("obsidian").Editor) { - const activeFile = this.app.workspace.getActiveFile(); - if (!activeFile) { new Notice("请先打开一个章节笔记"); return; } - - const content = editor.getValue(); - const meta = this.app.metadataCache.getFileCache(activeFile); - const fm = meta?.frontmatter; - - if (fm?.type !== "reading-note") { - new Notice("当前文件不是阅读章节笔记"); - return; - } - - const book = (fm.book as string) || "未知"; - const chapter = `第${fm.chapter}章:${fm.title}`; - - // 提取预设问题 - const questionsMatch = content.match(/## 读前问题\n([\s\S]*?)(?=\n## )/); - const questions = questionsMatch - ? questionsMatch[1].split("\n").filter((l) => l.trim().startsWith("- ")).map((l) => l.replace(/^- \[.\]\s*/, "").trim()) - : []; - - const notice = new Notice("⏳ 生成章节总结...", 0); - try { - const planner = new ReadingPlanner(this.settings); - const result = await planner.summarizeChapter(book, chapter, content, questions); - notice.hide(); - - const manager = new ReadingProjectManager(this.app, this.settings); - await manager.writeChapterSummary(activeFile, result); - new Notice("✅ 章节总结已生成"); - - // 刷新编辑器 - const leaf = this.app.workspace.getLeaf(false); - await leaf.openFile(activeFile); - } catch (err) { - notice.hide(); - new Notice(`❌ 生成失败:${(err as Error).message}`); - } - } - - private async runFeynmanTest(editor: import("obsidian").Editor) { - const activeFile = this.app.workspace.getActiveFile(); - if (!activeFile) { new Notice("请先打开一个章节笔记"); return; } - - const content = editor.getValue(); - const meta = this.app.metadataCache.getFileCache(activeFile); - const fm = meta?.frontmatter; - - if (fm?.type !== "reading-note") { - new Notice("当前文件不是阅读章节笔记"); - return; - } - - const book = (fm.book as string) || "未知"; - const chapter = `第${fm.chapter}章:${fm.title}`; - - // 提取概念 - const conceptsMatch = content.match(/## 关联概念\n([\s\S]*?)(?=\n## |$)/); - const concepts = conceptsMatch - ? conceptsMatch[1].match(/\[\[(.+?)\]\]/g)?.map((m) => m.replace(/\[\[|\]\]/g, "")) ?? [] - : []; - - const notice = new Notice("⏳ 生成检验问题...", 0); - try { - const planner = new ReadingPlanner(this.settings); - const questions = await planner.feynmanTest(book, chapter, concepts, content); - notice.hide(); - - new FeynmanModal(this.app, chapter, questions).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 生成失败:${(err as Error).message}`); - } - } - - // ── 智能补全 ───────────────────────────────────────────── - - /** - * 智能补全:自动判断场景 - * - 有选中文字 → 扩写 - * - 光标在空 section 内 → 补全该 section - * - 否则 → 续写 - */ - private async runSmartComplete(editor: import("obsidian").Editor) { - const selection = editor.getSelection().trim(); - - if (selection) { - // 场景 A:扩写选中内容 - const context = editor.getValue().slice(0, 1500); - await this.runExpand(selection, context, editor); - return; - } - - // 检查光标是否在空 section 内 - const cursor = editor.getCursor(); - const content = editor.getValue(); - const lines = content.split("\n"); - - // 向上找最近的 ## 标题 - let sectionName: string | null = null; - let sectionStartLine = -1; - for (let i = cursor.line; i >= 0; i--) { - const match = lines[i]?.match(/^##\s+(.+)/); - if (match) { - sectionName = match[1].trim(); - sectionStartLine = i; - break; - } - } - - if (sectionName && sectionStartLine >= 0) { - // 检查该 section 是否为空(标题到下一个 ## 之间没有非空行) - let sectionEmpty = true; - for (let i = sectionStartLine + 1; i < lines.length; i++) { - if (/^##\s/.test(lines[i])) break; - if (lines[i].trim().length > 0) { sectionEmpty = false; break; } - } - - if (sectionEmpty) { - // 场景 B:补全空 section - const activeFile = this.app.workspace.getActiveFile(); - const title = activeFile?.basename ?? "未知"; - await this.runSectionComplete(title, sectionName, content, sectionStartLine, editor); - return; - } - } - - // 场景 C:续写 - const beforeCursor = editor.getRange({ line: 0, ch: 0 }, cursor); - await this.runContinue(beforeCursor, editor); - } - - private async runExpand(selection: string, context: string, editor: import("obsidian").Editor) { - const notice = new Notice("⏳ 扩写中...", 0); - try { - const completer = new SmartCompleter(this.settings); - const result = await completer.expand(selection, context); - notice.hide(); - - new SmartPreviewModal( - this.app, - "扩写预览", - result.content, - () => { - // 替换选中内容 - editor.replaceSelection(result.content); - new Notice("✅ 已扩写"); - }, - () => { void this.runExpand(selection, context, editor); } - ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 扩写失败:${(err as Error).message}`); - } - } - - private async runContinue(beforeCursor: string, editor: import("obsidian").Editor) { - const notice = new Notice("⏳ 续写中...", 0); - try { - const completer = new SmartCompleter(this.settings); - const result = await completer.continueWriting(beforeCursor); - notice.hide(); - - new SmartPreviewModal( - this.app, - "续写预览", - result.content, - () => { - const cursor = editor.getCursor(); - editor.replaceRange("\n" + result.content, cursor); - new Notice("✅ 已续写"); - }, - () => { void this.runContinue(beforeCursor, editor); } - ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 续写失败:${(err as Error).message}`); - } - } - - private async runSectionComplete( - title: string, - sectionName: string, - fileContent: string, - sectionStartLine: number, - editor: import("obsidian").Editor - ) { - const notice = new Notice(`⏳ 补全"${sectionName}"...`, 0); - try { - const completer = new SmartCompleter(this.settings); - const result = await completer.completeSection(title, sectionName, fileContent); - notice.hide(); - - new SmartPreviewModal( - this.app, - `补全"${sectionName}"`, - result.content, - () => { - // 插入到 section 标题下方 - const insertLine = sectionStartLine + 1; - const insertPos = { line: insertLine, ch: 0 }; - editor.replaceRange(result.content + "\n\n", insertPos); - new Notice(`✅ 已补全"${sectionName}"`); - }, - () => { void this.runSectionComplete(title, sectionName, fileContent, sectionStartLine, editor); } - ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 补全失败:${(err as Error).message}`); - } - } - - private async runDocumentAnalysis(content: string, editor: import("obsidian").Editor) { - const notice = new Notice("⏳ 分析文档中...", 0); - try { - const completer = new SmartCompleter(this.settings); - const suggestions = await completer.analyzeDocument(content); - notice.hide(); - - new DocumentAnalysisModal(this.app, suggestions, (selected) => { - // 将选中的建议追加到文档末尾 - const parts = selected.map((s) => `## ${s.section}\n${s.content}`); - const insertText = "\n\n" + parts.join("\n\n") + "\n"; - const lastLine = editor.lastLine(); - editor.replaceRange(insertText, { line: lastLine, ch: editor.getLine(lastLine).length }); - new Notice(`✅ 已插入 ${selected.length} 处补充内容`); - }).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 分析失败:${(err as Error).message}`); - } - } - - // ── 图表/公式生成 ───────────────────────────────────────── - - private openDiagramGenerator(selection: string, context: string, editor: import("obsidian").Editor) { - new DiagramTypeModal(this.app, (type) => { - void this.runDiagramGeneration(selection, type, context, editor); - }).open(); - } - - private async runDiagramGeneration( - selection: string, - type: DiagramType, - context: string, - editor: import("obsidian").Editor - ) { - const notice = new Notice(`⏳ 生成${type === "auto" ? "图表" : type}中...`, 0); - - try { - const generator = new DiagramGenerator(this.settings); - const result = await generator.generate(selection, type, context); - notice.hide(); - - const formatted = generator.formatForInsert(result); - - new DiagramPreviewModal( - this.app, - result, - formatted, - () => { - // 插入到选中文字下方 - const cursor = editor.getCursor("to"); - const insertPos = { line: cursor.line + 1, ch: 0 }; - const insertText = `\n${formatted}\n`; - editor.replaceRange(insertText, insertPos); - new Notice(`✅ 已插入${result.typeName}`); - }, - () => { - void this.runDiagramGeneration(selection, type, context, editor); - }, - (instruction) => { - void this.runDiagramRefine(result.code, instruction, editor); - } - ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 生成失败:${(err as Error).message}`); - } - } - - private async runDiagramRefine( - existingCode: string, - instruction: string, - editor: import("obsidian").Editor - ) { + private async runDiagramRefine(existingCode: string, instruction: string, editor: import("obsidian").Editor) { const notice = new Notice("⏳ 优化图表中...", 0); - try { const generator = new DiagramGenerator(this.settings); const result = await generator.refine(existingCode, instruction); notice.hide(); - const formatted = generator.formatForInsert(result); - - new DiagramPreviewModal( - this.app, - result, - formatted, - () => { - const cursor = editor.getCursor("to"); - const insertPos = { line: cursor.line + 1, ch: 0 }; - editor.replaceRange(`\n${formatted}\n`, insertPos); - new Notice(`✅ 已插入优化后的${result.typeName}`); - }, - () => { - void this.runDiagramRefine(existingCode, instruction, editor); - }, - (newInstruction) => { - void this.runDiagramRefine(result.code, newInstruction, editor); - } + new DiagramPreviewModal(this.app, result, formatted, + () => { editor.replaceRange(`\n${formatted}\n`, { line: editor.getCursor("to").line + 1, ch: 0 }); new Notice(`✅ 已插入`); }, + () => { void this.runDiagramRefine(existingCode, instruction, editor); }, + (newInst) => { void this.runDiagramRefine(result.code, newInst, editor); } ).open(); - } catch (err) { - notice.hide(); - new Notice(`❌ 优化失败:${(err as Error).message}`); - } + } catch (err) { notice.hide(); new Notice(`❌ 优化失败:${(err as Error).message}`); } + } + + private async activateSyncView() { + const { workspace } = this.app; + let leaf = workspace.getLeavesOfType(SYNC_VIEW_TYPE)[0]; + if (!leaf) { leaf = workspace.getRightLeaf(false) ?? workspace.getLeaf(true); await leaf.setViewState({ type: SYNC_VIEW_TYPE, active: true }); } + await workspace.revealLeaf(leaf); } private async triggerAutoBackup(filePath: string) { const cfg = this.settings.baiduSync; if (!cfg.enabled || !cfg.autoBackup || !cfg.accessToken) return; - try { const service = new BaiduSyncService(this.app, cfg); const tokenOk = await service.ensureValidToken(); if (!tokenOk) return; - const folder = filePath.includes("/") ? filePath.substring(0, filePath.lastIndexOf("/")) : ""; await service.backup(folder); - } catch { - // 自动备份失败静默处理 - } + } catch { /* silent */ } } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); this.settings.baiduSync = Object.assign({}, DEFAULT_BAIDU_SYNC_CONFIG, this.settings.baiduSync); - - if (this.settings.baiduSync.enabled && this.settings.baiduSync.accessToken) { - void this.pullConfig(true); - } + if (this.settings.baiduSync.enabled && this.settings.baiduSync.accessToken) { void this.pullConfig(true); } } - async saveSettings() { - await this.saveData(this.settings); - } + async saveSettings() { await this.saveData(this.settings); } async pushConfig() { const cfg = this.settings.baiduSync; - if (!cfg.enabled || !cfg.accessToken) { - new Notice("请先启用百度云同步并完成授权"); - return; - } + if (!cfg.enabled || !cfg.accessToken) { new Notice("请先启用百度云同步并完成授权"); return; } const service = new BaiduSyncService(this.app, cfg); const adapter = this.app.vault.adapter as unknown as { basePath?: string }; - const basePath = adapter.basePath ?? "unknown-device"; - const ok = await service.pushConfig(this.settings, basePath); + const ok = await service.pushConfig(this.settings, adapter.basePath ?? "unknown-device"); new Notice(ok ? "✅ 配置已推送到百度云" : "❌ 配置推送失败"); } async pullConfig(silent = false) { const cfg = this.settings.baiduSync; if (!cfg.enabled || !cfg.accessToken) return; - const service = new BaiduSyncService(this.app, cfg); const remote = await service.pullConfig(undefined); - if (!remote) { - if (!silent) new Notice("远端无配置或已是最新"); - return; - } - + if (!remote) { if (!silent) new Notice("远端无配置或已是最新"); return; } this.settings = BaiduSyncService.applyRemoteConfig(this.settings, remote); await this.saveData(this.settings); - if (!silent) new Notice(`✅ 已从百度云拉取配置(由 ${remote.deviceId} 于 ${remote.updatedAt.slice(0, 10)} 推送)`); + if (!silent) new Notice(`✅ 已从百度云拉取配置`); } } diff --git a/versions.json b/versions.json index 03e5673..e5217c3 100644 --- a/versions.json +++ b/versions.json @@ -13,5 +13,6 @@ "1.7.0": "1.4.0", "1.7.1": "1.4.0", "1.7.2": "1.4.0", - "1.7.3": "1.4.0" + "1.7.3": "1.4.0", + "1.7.4": "1.4.0" }