mirror of
https://github.com/yan-istart/IStart-Note-AI-Plugin.git
synced 2026-07-22 06:51:37 +00:00
feat(panel): execution module visible + icon professionalization
Command panel now shows all three domains with actions:
入口: AI 助手 (pinned)
知识: 知识库问答, 知识提问, 补全概念, 扫描空概念, 阅读项目, 知识债务
执行: 生成执行计划, 查看待确认计划, 查看执行日志, 定时任务
辅助: 美化文档, 百度云同步
Execution module user-facing actions:
- 'generate-plan': AI analyzes current note, extracts action items,
creates PlanDraft (never auto-executes)
- 'view-pending-plans': opens most recent plan draft
- 'view-execution-logs': opens most recent execution record
- 'scheduled-tasks': notice placeholder until v2.1
Icons migrated from emoji to Lucide (Obsidian-native):
sparkles, book-open-check, message-circle-question, puzzle,
scan-search, book-marked, activity, list-todo, clipboard-list,
scroll-text, timer, paintbrush, cloud-upload
CommandPanelModal now uses setIcon() for proper SVG rendering.
Settings nav no longer uses emoji.
This commit is contained in:
parent
3720bdfbfe
commit
9d2a0c08ba
5 changed files with 193 additions and 22 deletions
|
|
@ -5,6 +5,8 @@ import { ActionDef } from "./types";
|
|||
*
|
||||
* The "AI 助手" is placed in Auxiliary as a cross-cutting entry point.
|
||||
* The command panel renders it as a pinned top-level button above the grouped actions.
|
||||
*
|
||||
* Icons use Lucide names (https://lucide.dev) which Obsidian supports natively.
|
||||
*/
|
||||
export const ALL_ACTIONS: ActionDef[] = [
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
|
@ -14,7 +16,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "vault-qa",
|
||||
label: "知识库问答",
|
||||
icon: "library",
|
||||
icon: "book-open-check",
|
||||
description: "基于 Vault 检索回答,附带来源引用",
|
||||
domain: "knowledge",
|
||||
section: "retrieval",
|
||||
|
|
@ -26,7 +28,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "question-with-graph",
|
||||
label: "知识提问",
|
||||
icon: "help-circle",
|
||||
icon: "message-circle-question",
|
||||
description: "提问 → 自动分类 → 生成 Q&A → 更新问题图谱",
|
||||
domain: "knowledge",
|
||||
section: "question",
|
||||
|
|
@ -37,7 +39,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "complete-current-concept",
|
||||
label: "补全当前概念页",
|
||||
icon: "book-plus",
|
||||
icon: "puzzle",
|
||||
description: "为当前打开的空概念页生成定义、解释、示例、关联",
|
||||
domain: "knowledge",
|
||||
section: "concept",
|
||||
|
|
@ -48,8 +50,8 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
},
|
||||
{
|
||||
id: "scan-empty-concepts",
|
||||
label: "扫描并补全空概念页",
|
||||
icon: "search",
|
||||
label: "扫描空概念页",
|
||||
icon: "scan-search",
|
||||
description: "扫描 Vault 中所有空概念页,批量补全",
|
||||
domain: "knowledge",
|
||||
section: "concept",
|
||||
|
|
@ -61,7 +63,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "new-reading-project",
|
||||
label: "新建阅读项目",
|
||||
icon: "book-open",
|
||||
icon: "book-marked",
|
||||
description: "输入书名,生成阅读地图",
|
||||
domain: "knowledge",
|
||||
section: "reading",
|
||||
|
|
@ -72,8 +74,8 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "knowledge-debt",
|
||||
label: "知识债务看板",
|
||||
icon: "bar-chart-2",
|
||||
description: "空概念、孤立问题、未完成阅读、长期未更新草稿",
|
||||
icon: "activity",
|
||||
description: "空概念、孤立问题、未完成阅读、长期草稿",
|
||||
domain: "knowledge",
|
||||
section: "debt",
|
||||
when: { always: true },
|
||||
|
|
@ -86,8 +88,52 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
// EXECUTION
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// Placeholder: future actions like "生成执行计划", "查看执行日志", "定时任务"
|
||||
// will be added here once runtime is implemented.
|
||||
{
|
||||
id: "generate-plan",
|
||||
label: "从当前笔记生成执行计划",
|
||||
icon: "list-todo",
|
||||
description: "AI 分析笔记内容,提取可执行行动项",
|
||||
domain: "execution",
|
||||
section: "plan",
|
||||
when: { always: true },
|
||||
showIn: ["panel", "editor-menu"],
|
||||
experimental: true,
|
||||
run: (ctx) => { ctx.plugin.openGeneratePlan(); },
|
||||
},
|
||||
{
|
||||
id: "view-pending-plans",
|
||||
label: "查看待确认计划",
|
||||
icon: "clipboard-list",
|
||||
description: "打开 Knowledge/_ExecutionPlans 中最新的计划草稿",
|
||||
domain: "execution",
|
||||
section: "plan",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openPendingPlans(); },
|
||||
},
|
||||
{
|
||||
id: "view-execution-logs",
|
||||
label: "查看执行日志",
|
||||
icon: "scroll-text",
|
||||
description: "打开 Knowledge/_Executions 中最新的执行记录",
|
||||
domain: "execution",
|
||||
section: "logs",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
run: (ctx) => { ctx.plugin.openExecutionLogs(); },
|
||||
},
|
||||
{
|
||||
id: "scheduled-tasks",
|
||||
label: "定时任务",
|
||||
icon: "timer",
|
||||
description: "查看和管理定时任务(v2.1 启用运行时)",
|
||||
domain: "execution",
|
||||
section: "scheduler",
|
||||
when: { always: true },
|
||||
showIn: ["panel"],
|
||||
experimental: true,
|
||||
run: (ctx) => { ctx.plugin.openScheduledTasks(); },
|
||||
},
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// AUXILIARY
|
||||
|
|
@ -97,7 +143,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
id: "ai-assistant",
|
||||
label: "AI 助手",
|
||||
icon: "sparkles",
|
||||
description: "选中文字或输入指令,AI 智能执行(跨模块入口)",
|
||||
description: "选中文字或输入指令,AI 智能执行",
|
||||
domain: "auxiliary",
|
||||
section: "assistant",
|
||||
when: { always: true },
|
||||
|
|
@ -107,7 +153,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "beautify-note",
|
||||
label: "美化当前文档",
|
||||
icon: "wand",
|
||||
icon: "paintbrush",
|
||||
description: "整理结构、插入 Callout、生成双链",
|
||||
domain: "auxiliary",
|
||||
section: "document",
|
||||
|
|
@ -118,7 +164,7 @@ export const ALL_ACTIONS: ActionDef[] = [
|
|||
{
|
||||
id: "baidu-sync",
|
||||
label: "百度云同步",
|
||||
icon: "cloud",
|
||||
icon: "cloud-upload",
|
||||
description: "备份 / 恢复 / 同步",
|
||||
domain: "auxiliary",
|
||||
section: "sync",
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ function openPanel(plugin: DeepSeekPlugin, actions: ActionDef[]) {
|
|||
// Add pinned as first "group" with a special title
|
||||
if (pinnedAction && evaluateWhen(pinnedAction.when, ctx)) {
|
||||
groups.push({
|
||||
title: "⭐ 入口",
|
||||
title: "入口",
|
||||
actions: [{
|
||||
id: pinnedAction.id,
|
||||
icon: pinnedAction.icon,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Modal, TFile } from "obsidian";
|
||||
import { App, Modal, setIcon, TFile } from "obsidian";
|
||||
|
||||
export interface PanelAction {
|
||||
id: string;
|
||||
|
|
@ -39,9 +39,9 @@ export class CommandPanelModal extends Modal {
|
|||
|
||||
for (const action of group.actions) {
|
||||
const row = groupEl.createDiv({ cls: "istart-panel-action" });
|
||||
const currentIndex = shortcutIndex;
|
||||
|
||||
row.createSpan({ text: `${action.icon}`, cls: "istart-panel-action-icon" });
|
||||
const iconEl = row.createSpan({ cls: "istart-panel-action-icon" });
|
||||
setIcon(iconEl, action.icon);
|
||||
|
||||
const textEl = row.createDiv({ cls: "istart-panel-action-text" });
|
||||
textEl.createEl("span", { text: action.label, cls: "istart-panel-action-label" });
|
||||
|
|
|
|||
125
src/main.ts
125
src/main.ts
|
|
@ -682,6 +682,131 @@ ${selection ? `用户当前选中的文字:\n${selection}\n` : ""}`;
|
|||
.map((f) => f.basename);
|
||||
}
|
||||
|
||||
// ── 执行模块入口 ───────────────────────────────────────────────
|
||||
|
||||
/** 打开待确认计划列表 */
|
||||
openPendingPlans() {
|
||||
const folder = "Knowledge/_ExecutionPlans";
|
||||
const files = this.app.vault.getMarkdownFiles().filter((f) => f.path.startsWith(folder));
|
||||
if (files.length === 0) {
|
||||
new Notice("暂无待确认计划");
|
||||
return;
|
||||
}
|
||||
// Open the folder in file explorer, or open the most recent plan
|
||||
const sorted = files.sort((a, b) => b.stat.mtime - a.stat.mtime);
|
||||
const leaf = this.app.workspace.getLeaf(false);
|
||||
void leaf.openFile(sorted[0]);
|
||||
if (files.length > 1) {
|
||||
new Notice(`共 ${files.length} 个待确认计划,已打开最新`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开执行日志列表 */
|
||||
openExecutionLogs() {
|
||||
const folder = "Knowledge/_Executions";
|
||||
const files = this.app.vault.getMarkdownFiles().filter((f) => f.path.startsWith(folder));
|
||||
if (files.length === 0) {
|
||||
new Notice("暂无执行日志");
|
||||
return;
|
||||
}
|
||||
const sorted = files.sort((a, b) => b.stat.mtime - a.stat.mtime);
|
||||
const leaf = this.app.workspace.getLeaf(false);
|
||||
void leaf.openFile(sorted[0]);
|
||||
if (files.length > 1) {
|
||||
new Notice(`共 ${files.length} 条执行记录,已打开最新`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看定时任务状态 */
|
||||
openScheduledTasks() {
|
||||
new Notice("定时任务运行时在 v2.0 默认关闭,将在 v2.1 通过设置页启用。");
|
||||
}
|
||||
|
||||
/** 从当前笔记生成执行计划(MVP:简单 prompt) */
|
||||
openGeneratePlan() {
|
||||
const editor = this.app.workspace.activeEditor?.editor ?? null;
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (!editor || !activeFile) {
|
||||
new Notice("请先打开一个文件");
|
||||
return;
|
||||
}
|
||||
const content = editor.getValue();
|
||||
if (!content.trim()) {
|
||||
new Notice("文档为空");
|
||||
return;
|
||||
}
|
||||
|
||||
new AssistantInputModal(this.app, `📋 从当前笔记生成执行计划`, (instruction) => {
|
||||
void this.runGeneratePlan(instruction, content, activeFile);
|
||||
}).open();
|
||||
}
|
||||
|
||||
private async runGeneratePlan(instruction: string, noteContent: string, sourceFile: TFile) {
|
||||
const notice = new Notice("⏳ AI 正在生成执行计划...", 0);
|
||||
try {
|
||||
const { LLMClient } = await import("./core/llm");
|
||||
const llm = new LLMClient(this.settings);
|
||||
|
||||
const systemPrompt = `你是一个任务规划助手。用户会给你一篇笔记内容和一条指令,请从中提取可执行的行动项,按以下 JSON 格式返回:
|
||||
{
|
||||
"title": "计划标题",
|
||||
"tasks": [
|
||||
{ "action": "create-file | append-section | create-link", "target": "目标文件路径或章节", "content": "内容" }
|
||||
]
|
||||
}
|
||||
规则:
|
||||
1. 只生成具体、可在 Obsidian 中执行的操作。
|
||||
2. target 使用 Obsidian 相对路径。
|
||||
3. 如果不确定,宁可少生成也不要编造。`;
|
||||
|
||||
const userPrompt = `笔记:${sourceFile.basename}\n\n内容:\n${noteContent.slice(0, 2000)}\n\n指令:${instruction}`;
|
||||
|
||||
const raw = await llm.chat({ systemPrompt, userPrompt, temperature: 0.4 });
|
||||
notice.hide();
|
||||
|
||||
// Parse and create a draft plan
|
||||
const { parseJsonSafe } = await import("./core/llm");
|
||||
const parsed = parseJsonSafe<{ title?: string; tasks?: { action?: string; target?: string; content?: string }[] } | null>(raw, null);
|
||||
|
||||
if (!parsed || !parsed.tasks || parsed.tasks.length === 0) {
|
||||
new Notice("AI 未能从当前笔记提取出可执行计划");
|
||||
return;
|
||||
}
|
||||
|
||||
const { PlanBuilder } = await import("./core/execution");
|
||||
const { PlanDraftStore } = await import("./core/execution");
|
||||
const builder = new PlanBuilder(parsed.title || instruction.slice(0, 40), "assistant");
|
||||
|
||||
for (const task of parsed.tasks) {
|
||||
if (!task.target || !task.content) continue;
|
||||
switch (task.action) {
|
||||
case "create-file":
|
||||
builder.createFile(task.target, task.content);
|
||||
break;
|
||||
case "append-section":
|
||||
builder.appendSection(task.target, "行动项", task.content);
|
||||
break;
|
||||
case "create-link":
|
||||
builder.createLink(sourceFile.path, task.target);
|
||||
break;
|
||||
default:
|
||||
builder.appendSection(task.target || sourceFile.path, "行动项", task.content);
|
||||
}
|
||||
}
|
||||
|
||||
const plan = builder.build();
|
||||
const store = new PlanDraftStore(this.app);
|
||||
const draftFile = await store.persistDraft(plan);
|
||||
|
||||
new Notice(`✅ 执行计划已生成(${plan.operations.length} 项操作),请审阅后确认`);
|
||||
const leaf = this.app.workspace.getLeaf(false);
|
||||
await leaf.openFile(draftFile);
|
||||
} catch (err) {
|
||||
notice.hide();
|
||||
new Notice(`❌ ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 定时任务 ─────────────────────────────────────────────────
|
||||
|
||||
private startScheduler(): void {
|
||||
|
|
|
|||
|
|
@ -34,17 +34,17 @@ export class DeepSeekSettingsTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
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: "🔧" },
|
||||
const sections: { id: SettingsSection; label: string }[] = [
|
||||
{ id: "knowledge", label: "知识" },
|
||||
{ id: "execution", label: "执行" },
|
||||
{ id: "auxiliary", label: "辅助" },
|
||||
];
|
||||
|
||||
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.setText(sec.label);
|
||||
item.addEventListener("click", () => {
|
||||
this.activeSection = sec.id;
|
||||
this.display();
|
||||
|
|
|
|||
Loading…
Reference in a new issue