diff options
| author | Adam Malczewski <[email protected]> | 2026-03-24 17:30:44 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-03-24 17:30:44 +0900 |
| commit | a5f54269f6b7ace71c4509fb8105993a7f064e63 (patch) | |
| tree | dfdfd0349cbfa357c02d26d02edbf3d6fe689872 | |
| parent | 3690c97ceaf8a20bb2c6d38bd600e5ae8bc2dac6 (diff) | |
| download | ai-pulse-obsidian-plugin-a5f54269f6b7ace71c4509fb8105993a7f064e63.tar.gz ai-pulse-obsidian-plugin-a5f54269f6b7ace71c4509fb8105993a7f064e63.zip | |
rename project
| -rw-r--r-- | .notes/research/plugin-feature-ideas.md | 2 | ||||
| -rw-r--r-- | .rules/default/obsidian.md | 4 | ||||
| -rw-r--r-- | .rules/default/ollama.md | 2 | ||||
| -rw-r--r-- | README.md | 14 | ||||
| -rw-r--r-- | manifest.json | 6 | ||||
| -rw-r--r-- | package-lock.json | 4 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | src/chat-view.ts | 148 | ||||
| -rw-r--r-- | src/main.ts | 8 | ||||
| -rw-r--r-- | src/settings-modal.ts | 16 | ||||
| -rw-r--r-- | src/settings.ts | 4 | ||||
| -rw-r--r-- | src/tool-modal.ts | 10 | ||||
| -rw-r--r-- | styles.css | 206 |
13 files changed, 214 insertions, 214 deletions
diff --git a/.notes/research/plugin-feature-ideas.md b/.notes/research/plugin-feature-ideas.md index ffd6752..fa76f62 100644 --- a/.notes/research/plugin-feature-ideas.md +++ b/.notes/research/plugin-feature-ideas.md @@ -1,6 +1,6 @@ # Plugin Feature Ideas -Ideas for the AI Note Organizer plugin, drawn from the Obsidian and Ollama APIs. +Ideas for the AI Pulse plugin, drawn from the Obsidian and Ollama APIs. --- diff --git a/.rules/default/obsidian.md b/.rules/default/obsidian.md index 0330edd..22fe06f 100644 --- a/.rules/default/obsidian.md +++ b/.rules/default/obsidian.md @@ -1,6 +1,6 @@ -# Obsidian API Docs — AI Note Organizer +# Obsidian API Docs — AI Pulse -This plugin organizes notes via AI powered by Ollama. Docs are in `.rules/docs/obsidian/`. +The Obsidian AI Note Management Plugin — powered by Ollama. Docs are in `.rules/docs/obsidian/`. ## Where to Look diff --git a/.rules/default/ollama.md b/.rules/default/ollama.md index 83562b8..0a6b559 100644 --- a/.rules/default/ollama.md +++ b/.rules/default/ollama.md @@ -1,4 +1,4 @@ -# Ollama API Docs — AI Note Organizer +# Ollama API Docs — AI Pulse Local LLM inference via Ollama (`http://localhost:11434`). Docs are in `.rules/docs/ollama/`. @@ -1,6 +1,6 @@ -# AI Organizer +# AI Pulse -An Obsidian plugin that organizes notes via AI powered by [Ollama](https://ollama.com). +The Obsidian AI Note Management Plugin — powered by [Ollama](https://ollama.com). ## Prerequisites @@ -11,8 +11,8 @@ An Obsidian plugin that organizes notes via AI powered by [Ollama](https://ollam ## Building from Source ```bash -git clone https://github.com/your-repo/aiorganizer_obsidian.git -cd aiorganizer_obsidian +git clone https://github.com/your-repo/ai-pulse.git +cd ai-pulse npm install npm run build ``` @@ -30,9 +30,9 @@ npm run dev 1. Build the plugin (see above). 2. Copy `main.js`, `styles.css`, and `manifest.json` into your vault at: ``` - <VaultFolder>/.obsidian/plugins/ai-organizer/ + <VaultFolder>/.obsidian/plugins/ai-pulse/ ``` -3. Open Obsidian, go to **Settings > Community Plugins**, and enable **AI Organizer**. +3. Open Obsidian, go to **Settings > Community Plugins**, and enable **AI Pulse**. ### Development Installation @@ -40,7 +40,7 @@ Clone or symlink this repo directly into your vault's plugin folder for live dev ```bash cd /path/to/your/vault/.obsidian/plugins -ln -s /path/to/aiorganizer_obsidian ai-organizer +ln -s /path/to/ai-pulse ai-pulse ``` Then run `npm run dev` and reload Obsidian to pick up changes. diff --git a/manifest.json b/manifest.json index 9ba100f..e2432e8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { - "id": "ai-organizer", - "name": "AI Organizer", + "id": "ai-pulse", + "name": "AI Pulse", "version": "1.0.0", "minAppVersion": "0.15.0", - "description": "Organize notes via AI powered by Ollama.", + "description": "The Obsidian AI Note Management Plugin — powered by Ollama.", "author": "Adam Malczewski", "authorUrl": "https://malcz.com", "isDesktopOnly": false diff --git a/package-lock.json b/package-lock.json index 32e7b1f..d94f1e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "ai-organizer", + "name": "ai-pulse", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ai-organizer", + "name": "ai-pulse", "version": "1.0.0", "license": "0-BSD", "dependencies": { diff --git a/package.json b/package.json index 5f99ba4..f77c8b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "ai-organizer", + "name": "ai-pulse", "version": "1.0.0", - "description": "Organize notes via AI powered by Ollama.", + "description": "The Obsidian AI Note Management Plugin \u2014 powered by Ollama.", "main": "main.js", "type": "module", "scripts": { diff --git a/src/chat-view.ts b/src/chat-view.ts index 7d34fcb..7975cd2 100644 --- a/src/chat-view.ts +++ b/src/chat-view.ts @@ -1,5 +1,5 @@ import { ItemView, MarkdownRenderer, Notice, TFile, WorkspaceLeaf, setIcon } from "obsidian"; -import type AIOrganizer from "./main"; +import type AIPulse from "./main"; import type { ChatMessage, ToolCallEvent, ApprovalRequestEvent } from "./ollama-client"; import { sendChatMessageStreaming } from "./ollama-client"; import { SettingsModal } from "./settings-modal"; @@ -7,10 +7,10 @@ import { ToolModal } from "./tool-modal"; import { TOOL_REGISTRY } from "./tools"; import type { OllamaToolDefinition } from "./tools"; -export const VIEW_TYPE_CHAT = "ai-organizer-chat"; +export const VIEW_TYPE_CHAT = "ai-pulse-chat"; export class ChatView extends ItemView { - private plugin: AIOrganizer; + private plugin: AIPulse; private messages: ChatMessage[] = []; private messageContainer: HTMLDivElement | null = null; private textarea: HTMLTextAreaElement | null = null; @@ -21,7 +21,7 @@ export class ChatView extends ItemView { private bubbleContent: Map<HTMLDivElement, string> = new Map(); private modelBadge: HTMLDivElement | null = null; - constructor(leaf: WorkspaceLeaf, plugin: AIOrganizer) { + constructor(leaf: WorkspaceLeaf, plugin: AIPulse) { super(leaf); this.plugin = plugin; } @@ -41,32 +41,32 @@ export class ChatView extends ItemView { async onOpen(): Promise<void> { const { contentEl } = this; contentEl.empty(); - contentEl.addClass("ai-organizer-chat-container"); + contentEl.addClass("ai-pulse-chat-container"); // --- Top region: Chat area --- - const messagesArea = contentEl.createDiv({ cls: "ai-organizer-messages-area" }); - this.messageContainer = messagesArea.createDiv({ cls: "ai-organizer-messages" }); + const messagesArea = contentEl.createDiv({ cls: "ai-pulse-messages-area" }); + this.messageContainer = messagesArea.createDiv({ cls: "ai-pulse-messages" }); // --- Model Badge (top left) --- - this.modelBadge = messagesArea.createDiv({ cls: "ai-organizer-model-badge" }); + this.modelBadge = messagesArea.createDiv({ cls: "ai-pulse-model-badge" }); this.updateModelBadge(); // --- FAB Speed Dial --- - const fab = messagesArea.createDiv({ cls: "ai-organizer-fab" }); + const fab = messagesArea.createDiv({ cls: "ai-pulse-fab" }); // Main FAB trigger button (first child) const fabTrigger = fab.createEl("button", { - cls: "ai-organizer-fab-trigger", + cls: "ai-pulse-fab-trigger", attr: { "aria-label": "Actions", tabindex: "0" }, }); setIcon(fabTrigger, "settings"); // Speed dial actions (revealed on focus-within) - const settingsAction = fab.createDiv({ cls: "ai-organizer-fab-action" }); - const settingsLabel = settingsAction.createSpan({ cls: "ai-organizer-fab-label", text: "AI Settings" }); + const settingsAction = fab.createDiv({ cls: "ai-pulse-fab-action" }); + const settingsLabel = settingsAction.createSpan({ cls: "ai-pulse-fab-label", text: "AI Settings" }); void settingsLabel; const settingsBtn = settingsAction.createEl("button", { - cls: "ai-organizer-fab-btn", + cls: "ai-pulse-fab-btn", attr: { "aria-label": "Settings" }, }); setIcon(settingsBtn, "sliders-horizontal"); @@ -80,11 +80,11 @@ export class ChatView extends ItemView { (document.activeElement as HTMLElement)?.blur(); }); - const toolsAction = fab.createDiv({ cls: "ai-organizer-fab-action" }); - const toolsLabel = toolsAction.createSpan({ cls: "ai-organizer-fab-label", text: "Tools" }); + const toolsAction = fab.createDiv({ cls: "ai-pulse-fab-action" }); + const toolsLabel = toolsAction.createSpan({ cls: "ai-pulse-fab-label", text: "Tools" }); void toolsLabel; this.toolsButton = toolsAction.createEl("button", { - cls: "ai-organizer-fab-btn", + cls: "ai-pulse-fab-btn", attr: { "aria-label": "Tools" }, }); setIcon(this.toolsButton, "wrench"); @@ -98,11 +98,11 @@ export class ChatView extends ItemView { (document.activeElement as HTMLElement)?.blur(); }); - const clearAction = fab.createDiv({ cls: "ai-organizer-fab-action" }); - const clearLabel = clearAction.createSpan({ cls: "ai-organizer-fab-label", text: "Clear Chat" }); + const clearAction = fab.createDiv({ cls: "ai-pulse-fab-action" }); + const clearLabel = clearAction.createSpan({ cls: "ai-pulse-fab-label", text: "Clear Chat" }); void clearLabel; const clearBtn = clearAction.createEl("button", { - cls: "ai-organizer-fab-btn", + cls: "ai-pulse-fab-btn", attr: { "aria-label": "Clear Chat" }, }); setIcon(clearBtn, "trash-2"); @@ -115,7 +115,7 @@ export class ChatView extends ItemView { (document.activeElement as HTMLElement)?.blur(); }); - const inputRow = messagesArea.createDiv({ cls: "ai-organizer-input-row" }); + const inputRow = messagesArea.createDiv({ cls: "ai-pulse-input-row" }); this.textarea = inputRow.createEl("textarea", { attr: { placeholder: "Type a message...", rows: "2" }, }); @@ -176,7 +176,7 @@ export class ChatView extends ItemView { private updateToolsButtonState(): void { if (this.toolsButton === null) return; - this.toolsButton.toggleClass("ai-organizer-tools-active", this.hasAnyToolEnabled()); + this.toolsButton.toggleClass("ai-pulse-tools-active", this.hasAnyToolEnabled()); } private updateModelBadge(): void { @@ -184,10 +184,10 @@ export class ChatView extends ItemView { const model = this.plugin.settings.model; if (model === "") { this.modelBadge.setText("No model selected"); - this.modelBadge.addClass("ai-organizer-model-badge-empty"); + this.modelBadge.addClass("ai-pulse-model-badge-empty"); } else { this.modelBadge.setText(model); - this.modelBadge.removeClass("ai-organizer-model-badge-empty"); + this.modelBadge.removeClass("ai-pulse-model-badge-empty"); } } @@ -267,7 +267,7 @@ export class ChatView extends ItemView { const onChunk = (chunk: string): void => { if (currentBubble !== null) { // Remove the loading indicator on first chunk - const loadingIcon = currentBubble.querySelector(".ai-organizer-loading-icon"); + const loadingIcon = currentBubble.querySelector(".ai-pulse-loading-icon"); if (loadingIcon !== null) { loadingIcon.remove(); } @@ -307,8 +307,8 @@ export class ChatView extends ItemView { } catch (err: unknown) { // Finalize bubble even on error if (currentBubble !== null) { - (currentBubble as HTMLDivElement).removeClass("ai-organizer-streaming"); - const errorIcon = (currentBubble as HTMLDivElement).querySelector(".ai-organizer-loading-icon"); + (currentBubble as HTMLDivElement).removeClass("ai-pulse-streaming"); + const errorIcon = (currentBubble as HTMLDivElement).querySelector(".ai-pulse-loading-icon"); if (errorIcon !== null) { errorIcon.remove(); } @@ -337,10 +337,10 @@ export class ChatView extends ItemView { throw new Error("Message container not initialized."); } const bubble = this.messageContainer.createDiv({ - cls: "ai-organizer-message assistant ai-organizer-streaming", + cls: "ai-pulse-message assistant ai-pulse-streaming", }); // Add a loading indicator icon - const iconSpan = bubble.createSpan({ cls: "ai-organizer-loading-icon" }); + const iconSpan = bubble.createSpan({ cls: "ai-pulse-loading-icon" }); setIcon(iconSpan, "more-horizontal"); return bubble; } @@ -350,10 +350,10 @@ export class ChatView extends ItemView { * and clean up the accumulated content tracker. */ private async finalizeBubble(bubble: HTMLDivElement): Promise<void> { - bubble.removeClass("ai-organizer-streaming"); + bubble.removeClass("ai-pulse-streaming"); // Remove loading icon if still present - const loadingIcon = bubble.querySelector(".ai-organizer-loading-icon"); + const loadingIcon = bubble.querySelector(".ai-pulse-loading-icon"); if (loadingIcon !== null) { loadingIcon.remove(); } @@ -369,8 +369,8 @@ export class ChatView extends ItemView { // Replace plain text with rendered markdown bubble.empty(); - bubble.removeClass("ai-organizer-streaming-text"); - bubble.addClass("ai-organizer-markdown"); + bubble.removeClass("ai-pulse-streaming-text"); + bubble.addClass("ai-pulse-markdown"); await MarkdownRenderer.render( this.plugin.app, rawText, @@ -400,8 +400,8 @@ export class ChatView extends ItemView { const cls = role === "error" - ? "ai-organizer-message assistant error" - : `ai-organizer-message ${role}`; + ? "ai-pulse-message assistant error" + : `ai-pulse-message ${role}`; this.messageContainer.createDiv({ cls, text: content }); } @@ -411,32 +411,32 @@ export class ChatView extends ItemView { return; } - const container = this.messageContainer.createDiv({ cls: "ai-organizer-tool-call" }); + const container = this.messageContainer.createDiv({ cls: "ai-pulse-tool-call" }); - const header = container.createDiv({ cls: "ai-organizer-tool-call-header" }); - setIcon(header.createSpan({ cls: "ai-organizer-tool-call-icon" }), "wrench"); - header.createSpan({ text: event.friendlyName, cls: "ai-organizer-tool-call-name" }); + const header = container.createDiv({ cls: "ai-pulse-tool-call-header" }); + setIcon(header.createSpan({ cls: "ai-pulse-tool-call-icon" }), "wrench"); + header.createSpan({ text: event.friendlyName, cls: "ai-pulse-tool-call-name" }); - container.createDiv({ text: event.summary, cls: "ai-organizer-tool-call-summary" }); - container.createDiv({ text: event.resultSummary, cls: "ai-organizer-tool-call-result-summary" }); + container.createDiv({ text: event.summary, cls: "ai-pulse-tool-call-summary" }); + container.createDiv({ text: event.resultSummary, cls: "ai-pulse-tool-call-result-summary" }); // DaisyUI-style collapse with checkbox - const collapse = container.createDiv({ cls: "ai-organizer-collapse ai-organizer-collapse-arrow" }); + const collapse = container.createDiv({ cls: "ai-pulse-collapse ai-pulse-collapse-arrow" }); const collapseId = `tool-collapse-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; const checkbox = collapse.createEl("input", { type: "checkbox", attr: { id: collapseId }, }); - checkbox.addClass("ai-organizer-collapse-toggle"); + checkbox.addClass("ai-pulse-collapse-toggle"); const titleEl = collapse.createEl("label", { - cls: "ai-organizer-collapse-title", + cls: "ai-pulse-collapse-title", attr: { for: collapseId }, text: "Details", }); void titleEl; // suppress unused warning - const collapseContent = collapse.createDiv({ cls: "ai-organizer-collapse-content" }); - const contentInner = collapseContent.createDiv({ cls: "ai-organizer-collapse-content-inner" }); + const collapseContent = collapse.createDiv({ cls: "ai-pulse-collapse-content" }); + const contentInner = collapseContent.createDiv({ cls: "ai-pulse-collapse-content-inner" }); if (event.toolName === "edit_file") { // For edit_file, show old_text / new_text in dedicated labeled blocks @@ -445,28 +445,28 @@ export class ChatView extends ItemView { const newText = typeof event.args.new_text === "string" ? event.args.new_text : ""; if (filePath !== "") { - contentInner.createEl("div", { text: `File: ${filePath}`, cls: "ai-organizer-tool-call-label" }); + contentInner.createEl("div", { text: `File: ${filePath}`, cls: "ai-pulse-tool-call-label" }); } - contentInner.createEl("div", { text: "Old text:", cls: "ai-organizer-tool-call-label" }); + contentInner.createEl("div", { text: "Old text:", cls: "ai-pulse-tool-call-label" }); contentInner.createEl("pre", { text: oldText === "" ? "(empty — new file)" : oldText, - cls: "ai-organizer-tool-call-args", + cls: "ai-pulse-tool-call-args", }); - contentInner.createEl("div", { text: "New text:", cls: "ai-organizer-tool-call-label" }); + contentInner.createEl("div", { text: "New text:", cls: "ai-pulse-tool-call-label" }); contentInner.createEl("pre", { text: newText, - cls: "ai-organizer-tool-call-result", + cls: "ai-pulse-tool-call-result", }); } else { const argsStr = JSON.stringify(event.args, null, 2); - contentInner.createEl("pre", { text: argsStr, cls: "ai-organizer-tool-call-args" }); + contentInner.createEl("pre", { text: argsStr, cls: "ai-pulse-tool-call-args" }); const resultPreview = event.result.length > 500 ? event.result.substring(0, 500) + "..." : event.result; - contentInner.createEl("pre", { text: resultPreview, cls: "ai-organizer-tool-call-result" }); + contentInner.createEl("pre", { text: resultPreview, cls: "ai-pulse-tool-call-result" }); } } @@ -477,78 +477,78 @@ export class ChatView extends ItemView { return; } - const container = this.messageContainer.createDiv({ cls: "ai-organizer-approval" }); + const container = this.messageContainer.createDiv({ cls: "ai-pulse-approval" }); - const header = container.createDiv({ cls: "ai-organizer-approval-header" }); - setIcon(header.createSpan({ cls: "ai-organizer-approval-icon" }), "shield-alert"); - header.createSpan({ text: event.friendlyName, cls: "ai-organizer-approval-name" }); + const header = container.createDiv({ cls: "ai-pulse-approval-header" }); + setIcon(header.createSpan({ cls: "ai-pulse-approval-icon" }), "shield-alert"); + header.createSpan({ text: event.friendlyName, cls: "ai-pulse-approval-name" }); - container.createDiv({ text: event.message, cls: "ai-organizer-approval-message" }); + container.createDiv({ text: event.message, cls: "ai-pulse-approval-message" }); // Show details for edit_file so the user can review the change if (event.toolName === "edit_file" || event.toolName === "create_file") { - const collapse = container.createDiv({ cls: "ai-organizer-collapse ai-organizer-collapse-arrow" }); + const collapse = container.createDiv({ cls: "ai-pulse-collapse ai-pulse-collapse-arrow" }); const collapseId = `approval-collapse-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; const checkbox = collapse.createEl("input", { type: "checkbox", attr: { id: collapseId, checked: "" }, }); - checkbox.addClass("ai-organizer-collapse-toggle"); + checkbox.addClass("ai-pulse-collapse-toggle"); checkbox.checked = true; const titleEl = collapse.createEl("label", { - cls: "ai-organizer-collapse-title", + cls: "ai-pulse-collapse-title", attr: { for: collapseId }, text: event.toolName === "create_file" ? "Review content" : "Review changes", }); void titleEl; - const collapseContent = collapse.createDiv({ cls: "ai-organizer-collapse-content" }); - const contentInner = collapseContent.createDiv({ cls: "ai-organizer-collapse-content-inner" }); + const collapseContent = collapse.createDiv({ cls: "ai-pulse-collapse-content" }); + const contentInner = collapseContent.createDiv({ cls: "ai-pulse-collapse-content-inner" }); if (event.toolName === "edit_file") { const oldText = typeof event.args.old_text === "string" ? event.args.old_text : ""; const newText = typeof event.args.new_text === "string" ? event.args.new_text : ""; - contentInner.createEl("div", { text: "Old text:", cls: "ai-organizer-tool-call-label" }); + contentInner.createEl("div", { text: "Old text:", cls: "ai-pulse-tool-call-label" }); contentInner.createEl("pre", { text: oldText === "" ? "(empty \u2014 new file)" : oldText, - cls: "ai-organizer-tool-call-args", + cls: "ai-pulse-tool-call-args", }); - contentInner.createEl("div", { text: "New text:", cls: "ai-organizer-tool-call-label" }); + contentInner.createEl("div", { text: "New text:", cls: "ai-pulse-tool-call-label" }); contentInner.createEl("pre", { text: newText, - cls: "ai-organizer-tool-call-result", + cls: "ai-pulse-tool-call-result", }); } else { // create_file const content = typeof event.args.content === "string" ? event.args.content : ""; - contentInner.createEl("div", { text: "Content:", cls: "ai-organizer-tool-call-label" }); + contentInner.createEl("div", { text: "Content:", cls: "ai-pulse-tool-call-label" }); contentInner.createEl("pre", { text: content === "" ? "(empty file)" : content, - cls: "ai-organizer-tool-call-result", + cls: "ai-pulse-tool-call-result", }); } } - const buttonRow = container.createDiv({ cls: "ai-organizer-approval-buttons" }); + const buttonRow = container.createDiv({ cls: "ai-pulse-approval-buttons" }); const approveBtn = buttonRow.createEl("button", { text: "Approve", - cls: "ai-organizer-approval-approve", + cls: "ai-pulse-approval-approve", }); const declineBtn = buttonRow.createEl("button", { text: "Decline", - cls: "ai-organizer-approval-decline", + cls: "ai-pulse-approval-decline", }); const finalize = (approved: boolean): void => { approveBtn.disabled = true; declineBtn.disabled = true; - container.addClass(approved ? "ai-organizer-approval-approved" : "ai-organizer-approval-declined"); - const statusEl = container.createDiv({ cls: "ai-organizer-approval-status" }); + container.addClass(approved ? "ai-pulse-approval-approved" : "ai-pulse-approval-declined"); + const statusEl = container.createDiv({ cls: "ai-pulse-approval-status" }); statusEl.setText(approved ? "Approved" : "Declined"); this.scrollToBottom(); resolve(approved); @@ -581,7 +581,7 @@ export class ChatView extends ItemView { } if (this.sendButton !== null) { this.sendButton.textContent = streaming ? "Stop" : "Send"; - this.sendButton.toggleClass("ai-organizer-stop-btn", streaming); + this.sendButton.toggleClass("ai-pulse-stop-btn", streaming); } } } diff --git a/src/main.ts b/src/main.ts index d120bdf..e66a126 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,11 @@ import { Plugin, WorkspaceLeaf } from "obsidian"; -import { AIOrganizerSettings, DEFAULT_SETTINGS } from "./settings"; +import { AIPulseSettings, DEFAULT_SETTINGS } from "./settings"; import { ChatView, VIEW_TYPE_CHAT } from "./chat-view"; import { testConnection, listModels } from "./ollama-client"; import { getDefaultToolStates } from "./tools"; -export default class AIOrganizer extends Plugin { - settings: AIOrganizerSettings = DEFAULT_SETTINGS; +export default class AIPulse extends Plugin { + settings: AIPulseSettings = DEFAULT_SETTINGS; // Runtime connection state (not persisted) connectionStatus: "disconnected" | "connecting" | "connected" | "error" = "disconnected"; @@ -57,7 +57,7 @@ export default class AIOrganizer extends Plugin { this.settings = Object.assign( {}, DEFAULT_SETTINGS, - await this.loadData() as Partial<AIOrganizerSettings> | null, + await this.loadData() as Partial<AIPulseSettings> | null, ); // Ensure enabledTools has entries for all registered tools this.settings.enabledTools = Object.assign( diff --git a/src/settings-modal.ts b/src/settings-modal.ts index e475c3f..9a4218c 100644 --- a/src/settings-modal.ts +++ b/src/settings-modal.ts @@ -1,15 +1,15 @@ import { Modal, Setting } from "obsidian"; -import type AIOrganizer from "./main"; +import type AIPulse from "./main"; import { showModel } from "./ollama-client"; import type { ModelInfo } from "./ollama-client"; export class SettingsModal extends Modal { - private plugin: AIOrganizer; + private plugin: AIPulse; private modelInfo: ModelInfo | null = null; private ctxMaxEl: HTMLElement | null = null; private ctxInputEl: HTMLInputElement | null = null; - constructor(plugin: AIOrganizer) { + constructor(plugin: AIPulse) { super(plugin.app); this.plugin = plugin; } @@ -17,7 +17,7 @@ export class SettingsModal extends Modal { onOpen(): void { const { contentEl } = this; contentEl.empty(); - contentEl.addClass("ai-organizer-settings-modal"); + contentEl.addClass("ai-pulse-settings-modal"); this.setTitle("AI Settings"); @@ -97,7 +97,7 @@ export class SettingsModal extends Modal { }); const updateFileSettingState = (enabled: boolean): void => { - fileSetting.settingEl.toggleClass("ai-organizer-setting-disabled", !enabled); + fileSetting.settingEl.toggleClass("ai-pulse-setting-disabled", !enabled); if (fileInputEl !== null) { fileInputEl.disabled = !enabled; } @@ -135,7 +135,7 @@ export class SettingsModal extends Modal { .setDesc("Controls randomness. Lower = more focused, higher = more creative."); const tempValueEl = tempSetting.descEl.createSpan({ - cls: "ai-organizer-param-value", + cls: "ai-pulse-param-value", text: ` (${this.plugin.settings.temperature.toFixed(2)})`, }); @@ -180,7 +180,7 @@ export class SettingsModal extends Modal { const ctxControlEl = ctxSetting.controlEl; ctxControlEl.style.flexDirection = "column"; ctxControlEl.style.alignItems = "flex-end"; - this.ctxMaxEl = ctxControlEl.createDiv({ cls: "ai-organizer-ctx-max" }); + this.ctxMaxEl = ctxControlEl.createDiv({ cls: "ai-pulse-ctx-max" }); this.ctxMaxEl.style.cursor = "pointer"; this.ctxMaxEl.addEventListener("click", async () => { if (this.modelInfo !== null && this.ctxInputEl !== null) { @@ -242,7 +242,7 @@ export class SettingsModal extends Modal { private updateCtxMaxWarning(): void { if (this.ctxMaxEl === null || this.modelInfo === null) return; const exceeds = this.plugin.settings.numCtx > this.modelInfo.contextLength; - this.ctxMaxEl.toggleClass("ai-organizer-ctx-max-warn", exceeds); + this.ctxMaxEl.toggleClass("ai-pulse-ctx-max-warn", exceeds); } private populateModelDropdown(selectEl: HTMLSelectElement): void { diff --git a/src/settings.ts b/src/settings.ts index 8e73770..eb42c3f 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,6 +1,6 @@ import { getDefaultToolStates } from "./tools"; -export interface AIOrganizerSettings { +export interface AIPulseSettings { ollamaUrl: string; model: string; enabledTools: Record<string, boolean>; @@ -11,7 +11,7 @@ export interface AIOrganizerSettings { systemPromptFile: string; } -export const DEFAULT_SETTINGS: AIOrganizerSettings = { +export const DEFAULT_SETTINGS: AIPulseSettings = { ollamaUrl: "http://localhost:11434", model: "", enabledTools: getDefaultToolStates(), diff --git a/src/tool-modal.ts b/src/tool-modal.ts index c1b1522..4da8edb 100644 --- a/src/tool-modal.ts +++ b/src/tool-modal.ts @@ -1,11 +1,11 @@ import { Modal, Setting } from "obsidian"; -import type AIOrganizer from "./main"; +import type AIPulse from "./main"; import { TOOL_REGISTRY } from "./tools"; export class ToolModal extends Modal { - private plugin: AIOrganizer; + private plugin: AIPulse; - constructor(plugin: AIOrganizer) { + constructor(plugin: AIPulse) { super(plugin.app); this.plugin = plugin; } @@ -13,13 +13,13 @@ export class ToolModal extends Modal { onOpen(): void { const { contentEl } = this; contentEl.empty(); - contentEl.addClass("ai-organizer-tool-modal"); + contentEl.addClass("ai-pulse-tool-modal"); this.setTitle("AI Tools"); contentEl.createEl("p", { text: "Enable tools to give the AI access to your vault. Changes take effect on the next message.", - cls: "ai-organizer-tool-modal-desc", + cls: "ai-pulse-tool-modal-desc", }); for (const tool of TOOL_REGISTRY) { @@ -1,10 +1,10 @@ -.ai-organizer-chat-container { +.ai-pulse-chat-container { display: flex; flex-direction: column; height: 100%; } -.ai-organizer-messages-area { +.ai-pulse-messages-area { flex: 1; display: flex; flex-direction: column; @@ -13,7 +13,7 @@ position: relative; } -.ai-organizer-messages { +.ai-pulse-messages { flex: 1; overflow-y: auto; padding: 8px; @@ -23,7 +23,7 @@ gap: 6px; } -.ai-organizer-message { +.ai-pulse-message { padding: 8px 12px; border-radius: 8px; max-width: 85%; @@ -33,47 +33,47 @@ -webkit-user-select: text; } -.ai-organizer-message.user { +.ai-pulse-message.user { align-self: flex-end; background-color: var(--interactive-accent); color: var(--text-on-accent); } -.ai-organizer-message.assistant { +.ai-pulse-message.assistant { align-self: flex-start; background-color: var(--background-primary); border: 1px solid var(--background-modifier-border); } -.ai-organizer-streaming { +.ai-pulse-streaming { opacity: 0.85; } -.ai-organizer-loading-icon { +.ai-pulse-loading-icon { display: flex; align-items: center; color: var(--text-muted); } -.ai-organizer-loading-icon svg { +.ai-pulse-loading-icon svg { width: 20px; height: 20px; } -.ai-organizer-message.error { +.ai-pulse-message.error { color: var(--text-error); } /* ===== Input Row ===== */ -.ai-organizer-input-row { +.ai-pulse-input-row { display: flex; flex-direction: row; gap: 6px; padding: 8px; } -.ai-organizer-input-row textarea { +.ai-pulse-input-row textarea { flex: 1; resize: vertical; background-color: var(--background-primary); @@ -85,14 +85,14 @@ font-size: inherit; } -.ai-organizer-input-row textarea:focus { +.ai-pulse-input-row textarea:focus { border-color: var(--interactive-accent); outline: none; } /* ===== FAB / Speed Dial ===== */ -.ai-organizer-fab { +.ai-pulse-fab { position: absolute; top: 8px; right: 12px; @@ -104,12 +104,12 @@ pointer-events: none; } -.ai-organizer-fab > * { +.ai-pulse-fab > * { pointer-events: auto; } /* Main FAB trigger */ -.ai-organizer-fab-trigger { +.ai-pulse-fab-trigger { width: 40px; height: 40px; border-radius: 50%; @@ -125,22 +125,22 @@ background-color 0.2s; } -.ai-organizer-fab-trigger:hover { +.ai-pulse-fab-trigger:hover { background-color: var(--interactive-accent-hover); } -.ai-organizer-fab-trigger svg { +.ai-pulse-fab-trigger svg { width: 18px; height: 18px; } /* Rotate trigger when FAB is open */ -.ai-organizer-fab:focus-within > .ai-organizer-fab-trigger { +.ai-pulse-fab:focus-within > .ai-pulse-fab-trigger { transform: rotate(90deg); } /* FAB action items (hidden by default) */ -.ai-organizer-fab-action { +.ai-pulse-fab-action { display: flex; align-items: center; gap: 8px; @@ -153,27 +153,27 @@ } /* Staggered delays */ -.ai-organizer-fab-action:nth-child(2) { +.ai-pulse-fab-action:nth-child(2) { transition-delay: 30ms; } -.ai-organizer-fab-action:nth-child(3) { +.ai-pulse-fab-action:nth-child(3) { transition-delay: 60ms; } -.ai-organizer-fab-action:nth-child(4) { +.ai-pulse-fab-action:nth-child(4) { transition-delay: 90ms; } /* Reveal actions on focus-within */ -.ai-organizer-fab:focus-within > .ai-organizer-fab-action { +.ai-pulse-fab:focus-within > .ai-pulse-fab-action { visibility: visible; opacity: 1; transform: scale(1); } /* FAB action label */ -.ai-organizer-fab-label { +.ai-pulse-fab-label { padding: 3px 8px; border-radius: 4px; background-color: var(--background-secondary); @@ -184,7 +184,7 @@ } /* FAB action button (circle) */ -.ai-organizer-fab-btn { +.ai-pulse-fab-btn { width: 40px; height: 40px; border-radius: 50%; @@ -199,19 +199,19 @@ transition: background-color 0.2s, color 0.2s, border-color 0.2s; } -.ai-organizer-fab-btn:hover { +.ai-pulse-fab-btn:hover { color: var(--text-normal); background-color: var(--background-modifier-hover); } -.ai-organizer-fab-btn svg { +.ai-pulse-fab-btn svg { width: 18px; height: 18px; } /* ===== Tool Call Bubbles ===== */ -.ai-organizer-tool-call { +.ai-pulse-tool-call { align-self: flex-start; max-width: 85%; padding: 6px 10px; @@ -224,7 +224,7 @@ -webkit-user-select: text; } -.ai-organizer-tool-call-header { +.ai-pulse-tool-call-header { display: flex; align-items: center; gap: 6px; @@ -232,27 +232,27 @@ color: var(--text-muted); } -.ai-organizer-tool-call-icon { +.ai-pulse-tool-call-icon { display: flex; align-items: center; } -.ai-organizer-tool-call-icon svg { +.ai-pulse-tool-call-icon svg { width: 14px; height: 14px; } -.ai-organizer-tool-call-name { +.ai-pulse-tool-call-name { font-weight: 600; } -.ai-organizer-tool-call-summary { +.ai-pulse-tool-call-summary { margin: 4px 0 2px 0; color: var(--text-normal); font-style: italic; } -.ai-organizer-tool-call-result-summary { +.ai-pulse-tool-call-result-summary { margin: 0 0 0 0; color: var(--text-muted); font-size: 0.9em; @@ -260,7 +260,7 @@ /* ===== DaisyUI-inspired Collapse ===== */ -.ai-organizer-collapse { +.ai-pulse-collapse { display: grid; position: relative; overflow: hidden; @@ -272,7 +272,7 @@ margin-top: 2px; } -.ai-organizer-collapse-toggle { +.ai-pulse-collapse-toggle { position: absolute; opacity: 0; width: 0; @@ -280,7 +280,7 @@ pointer-events: none; } -.ai-organizer-collapse-title { +.ai-pulse-collapse-title { grid-column-start: 1; grid-row-start: 1; position: relative; @@ -293,12 +293,12 @@ transition: color 0.15s; } -.ai-organizer-collapse-title:hover { +.ai-pulse-collapse-title:hover { color: var(--text-normal); } /* Collapse arrow indicator */ -.ai-organizer-collapse-arrow > .ai-organizer-collapse-title::after { +.ai-pulse-collapse-arrow > .ai-pulse-collapse-title::after { content: ""; position: absolute; top: 50%; @@ -314,16 +314,16 @@ } /* Arrow rotates when open */ -.ai-organizer-collapse-toggle:checked ~ .ai-organizer-collapse-title::after { +.ai-pulse-collapse-toggle:checked ~ .ai-pulse-collapse-title::after { transform: translateY(-25%) rotate(225deg); } /* Expand grid when checked */ -.ai-organizer-collapse:has(.ai-organizer-collapse-toggle:checked) { +.ai-pulse-collapse:has(.ai-pulse-collapse-toggle:checked) { grid-template-rows: max-content 1fr; } -.ai-organizer-collapse-content { +.ai-pulse-collapse-content { grid-column-start: 1; grid-row-start: 2; min-height: 0; @@ -331,16 +331,16 @@ transition: min-height 0.2s ease-out; } -.ai-organizer-collapse-toggle:checked ~ .ai-organizer-collapse-content { +.ai-pulse-collapse-toggle:checked ~ .ai-pulse-collapse-content { min-height: fit-content; } -.ai-organizer-collapse-content-inner { +.ai-pulse-collapse-content-inner { padding: 2px 0 4px 0; } -.ai-organizer-tool-call-args, -.ai-organizer-tool-call-result { +.ai-pulse-tool-call-args, +.ai-pulse-tool-call-result { margin: 2px 0; padding: 4px 6px; border-radius: 4px; @@ -352,69 +352,69 @@ overflow-y: auto; } -.ai-organizer-tool-call-result { +.ai-pulse-tool-call-result { color: var(--text-muted); } -.ai-organizer-tool-call-label { +.ai-pulse-tool-call-label { font-weight: 600; color: var(--text-muted); font-size: 0.85em; margin: 6px 0 2px 0; } -.ai-organizer-tool-call-label:first-child { +.ai-pulse-tool-call-label:first-child { margin-top: 0; } /* ===== Markdown in Assistant Bubbles ===== */ -.ai-organizer-message.assistant.ai-organizer-markdown { +.ai-pulse-message.assistant.ai-pulse-markdown { white-space: normal; } -.ai-organizer-message.assistant.ai-organizer-markdown p { +.ai-pulse-message.assistant.ai-pulse-markdown p { margin: 0 0 0.4em 0; } -.ai-organizer-message.assistant.ai-organizer-markdown p:last-child { +.ai-pulse-message.assistant.ai-pulse-markdown p:last-child { margin-bottom: 0; } -.ai-organizer-message.assistant.ai-organizer-markdown h1, -.ai-organizer-message.assistant.ai-organizer-markdown h2, -.ai-organizer-message.assistant.ai-organizer-markdown h3, -.ai-organizer-message.assistant.ai-organizer-markdown h4, -.ai-organizer-message.assistant.ai-organizer-markdown h5, -.ai-organizer-message.assistant.ai-organizer-markdown h6 { +.ai-pulse-message.assistant.ai-pulse-markdown h1, +.ai-pulse-message.assistant.ai-pulse-markdown h2, +.ai-pulse-message.assistant.ai-pulse-markdown h3, +.ai-pulse-message.assistant.ai-pulse-markdown h4, +.ai-pulse-message.assistant.ai-pulse-markdown h5, +.ai-pulse-message.assistant.ai-pulse-markdown h6 { margin: 0.4em 0 0.2em 0; font-size: 1em; line-height: 1.3; } -.ai-organizer-message.assistant.ai-organizer-markdown h1 { +.ai-pulse-message.assistant.ai-pulse-markdown h1 { font-size: 1.15em; } -.ai-organizer-message.assistant.ai-organizer-markdown h2 { +.ai-pulse-message.assistant.ai-pulse-markdown h2 { font-size: 1.1em; } -.ai-organizer-message.assistant.ai-organizer-markdown h3 { +.ai-pulse-message.assistant.ai-pulse-markdown h3 { font-size: 1.05em; } -.ai-organizer-message.assistant.ai-organizer-markdown ul, -.ai-organizer-message.assistant.ai-organizer-markdown ol { +.ai-pulse-message.assistant.ai-pulse-markdown ul, +.ai-pulse-message.assistant.ai-pulse-markdown ol { margin: 0.2em 0; padding-left: 1.4em; } -.ai-organizer-message.assistant.ai-organizer-markdown li { +.ai-pulse-message.assistant.ai-pulse-markdown li { margin: 0.1em 0; } -.ai-organizer-message.assistant.ai-organizer-markdown pre { +.ai-pulse-message.assistant.ai-pulse-markdown pre { margin: 0.3em 0; padding: 6px 8px; border-radius: 4px; @@ -425,88 +425,88 @@ word-wrap: break-word; } -.ai-organizer-message.assistant.ai-organizer-markdown code { +.ai-pulse-message.assistant.ai-pulse-markdown code { padding: 1px 4px; border-radius: 3px; background-color: var(--background-secondary); font-size: 0.9em; } -.ai-organizer-message.assistant.ai-organizer-markdown pre code { +.ai-pulse-message.assistant.ai-pulse-markdown pre code { padding: 0; background-color: transparent; font-size: inherit; } -.ai-organizer-message.assistant.ai-organizer-markdown blockquote { +.ai-pulse-message.assistant.ai-pulse-markdown blockquote { margin: 0.3em 0; padding: 2px 8px; border-left: 3px solid var(--interactive-accent); color: var(--text-muted); } -.ai-organizer-message.assistant.ai-organizer-markdown table { +.ai-pulse-message.assistant.ai-pulse-markdown table { border-collapse: collapse; margin: 0.3em 0; font-size: 0.9em; width: 100%; } -.ai-organizer-message.assistant.ai-organizer-markdown th, -.ai-organizer-message.assistant.ai-organizer-markdown td { +.ai-pulse-message.assistant.ai-pulse-markdown th, +.ai-pulse-message.assistant.ai-pulse-markdown td { padding: 3px 6px; border: 1px solid var(--background-modifier-border); } -.ai-organizer-message.assistant.ai-organizer-markdown th { +.ai-pulse-message.assistant.ai-pulse-markdown th { background-color: var(--background-secondary); font-weight: 600; } -.ai-organizer-message.assistant.ai-organizer-markdown hr { +.ai-pulse-message.assistant.ai-pulse-markdown hr { margin: 0.4em 0; border: none; border-top: 1px solid var(--background-modifier-border); } -.ai-organizer-message.assistant.ai-organizer-markdown img { +.ai-pulse-message.assistant.ai-pulse-markdown img { max-width: 100%; border-radius: 4px; } /* ===== Misc ===== */ -.ai-organizer-tool-modal-desc { +.ai-pulse-tool-modal-desc { color: var(--text-muted); font-size: 0.9em; margin-bottom: 8px; } -.ai-organizer-stop-btn { +.ai-pulse-stop-btn { background-color: var(--text-error) !important; color: var(--text-on-accent) !important; border-color: var(--text-error) !important; } -.ai-organizer-param-value { +.ai-pulse-param-value { font-weight: 600; color: var(--text-normal); } -.ai-organizer-ctx-max { +.ai-pulse-ctx-max { font-size: 0.8em; color: var(--text-muted); margin-top: 4px; text-align: right; } -.ai-organizer-ctx-max-warn { +.ai-pulse-ctx-max-warn { color: var(--text-error); } /* ===== Tool Approval Prompt ===== */ -.ai-organizer-approval { +.ai-pulse-approval { align-self: flex-start; max-width: 85%; padding: 8px 12px; @@ -516,7 +516,7 @@ margin: 2px 0; } -.ai-organizer-approval-header { +.ai-pulse-approval-header { display: flex; align-items: center; gap: 6px; @@ -524,34 +524,34 @@ color: var(--text-warning); } -.ai-organizer-approval-icon { +.ai-pulse-approval-icon { display: flex; align-items: center; } -.ai-organizer-approval-icon svg { +.ai-pulse-approval-icon svg { width: 16px; height: 16px; } -.ai-organizer-approval-name { +.ai-pulse-approval-name { font-weight: 600; font-size: 0.9em; } -.ai-organizer-approval-message { +.ai-pulse-approval-message { margin: 4px 0 8px 0; color: var(--text-normal); font-size: 0.9em; } -.ai-organizer-approval-buttons { +.ai-pulse-approval-buttons { display: flex; gap: 8px; } -.ai-organizer-approval-approve, -.ai-organizer-approval-decline { +.ai-pulse-approval-approve, +.ai-pulse-approval-decline { padding: 4px 16px; border-radius: 4px; border: none; @@ -561,62 +561,62 @@ transition: opacity 0.15s; } -.ai-organizer-approval-approve { +.ai-pulse-approval-approve { background-color: var(--interactive-accent); color: var(--text-on-accent); } -.ai-organizer-approval-approve:hover { +.ai-pulse-approval-approve:hover { opacity: 0.85; } -.ai-organizer-approval-decline { +.ai-pulse-approval-decline { background-color: var(--background-modifier-border); color: var(--text-normal); } -.ai-organizer-approval-decline:hover { +.ai-pulse-approval-decline:hover { opacity: 0.85; } -.ai-organizer-approval-approve:disabled, -.ai-organizer-approval-decline:disabled { +.ai-pulse-approval-approve:disabled, +.ai-pulse-approval-decline:disabled { opacity: 0.5; cursor: default; } -.ai-organizer-approval-status { +.ai-pulse-approval-status { margin-top: 6px; font-size: 0.8em; font-weight: 600; } -.ai-organizer-approval-approved .ai-organizer-approval-status { +.ai-pulse-approval-approved .ai-pulse-approval-status { color: var(--interactive-accent); } -.ai-organizer-approval-declined .ai-organizer-approval-status { +.ai-pulse-approval-declined .ai-pulse-approval-status { color: var(--text-error); } -.ai-organizer-approval-approved { +.ai-pulse-approval-approved { border-left-color: var(--interactive-accent); } -.ai-organizer-approval-declined { +.ai-pulse-approval-declined { border-left-color: var(--text-error); } /* ===== Disabled Setting ===== */ -.ai-organizer-setting-disabled { +.ai-pulse-setting-disabled { opacity: 0.5; pointer-events: none; } /* ===== Model Badge ===== */ -.ai-organizer-model-badge { +.ai-pulse-model-badge { position: absolute; top: 8px; left: 12px; @@ -640,7 +640,7 @@ user-select: none; } -.ai-organizer-model-badge-empty { +.ai-pulse-model-badge-empty { color: var(--text-muted); font-style: italic; } |
