From bb543c3f7840f2a3fa1b7a1fb32245fa87a30f7b Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 24 Mar 2026 13:18:50 +0900 Subject: initial prototype --- .rules/changelog/2026-03/24/05.md | 35 ++++++ .rules/plan/plan.md | 255 -------------------------------------- 2 files changed, 35 insertions(+), 255 deletions(-) create mode 100644 .rules/changelog/2026-03/24/05.md delete mode 100644 .rules/plan/plan.md (limited to '.rules') diff --git a/.rules/changelog/2026-03/24/05.md b/.rules/changelog/2026-03/24/05.md new file mode 100644 index 0000000..7970687 --- /dev/null +++ b/.rules/changelog/2026-03/24/05.md @@ -0,0 +1,35 @@ +# Changelog — 2026-03-24 #05 + +## Settings moved from sidebar panel to modal + +### Changes + +- **New file: `src/settings-modal.ts`** + - Created `SettingsModal` class extending Obsidian `Modal` + - Contains Ollama URL, Connect button, and Model dropdown settings + - Reads shared connection state from the plugin instance + - Helper method `populateModelDropdown()` for reusable dropdown population + +- **`src/chat-view.ts`** + - Removed inline settings panel from bottom of chat sidebar + - Added a gear (⚙) settings button next to the Send button in the input row + - Clicking the gear button opens the `SettingsModal` + - Auto-connects to Ollama when the chat view opens via `plugin.connect()` + - Removed unused imports (`Setting`, `testConnection`, `listModels`) + - Added imports for `setIcon` and `SettingsModal` + +- **`src/main.ts`** + - Added runtime connection state fields: `connectionStatus`, `connectionMessage`, `availableModels` + - Added `connect()` method that tests connection, fetches available models, and stores results + - Added `testConnection` and `listModels` imports + +- **`styles.css`** + - Removed `.ai-organizer-settings-panel` styles + - Added `.ai-organizer-input-buttons` vertical button group layout + - Added `.ai-organizer-settings-btn` with transparent background and hover state + +### Summary + +- "Test Connection" renamed to "Connect" +- Settings are now in a modal opened via a gear button in the sidebar input row +- Auto-connect runs when the chat view opens, pre-populating models for the settings modal diff --git a/.rules/plan/plan.md b/.rules/plan/plan.md deleted file mode 100644 index f4106ac..0000000 --- a/.rules/plan/plan.md +++ /dev/null @@ -1,255 +0,0 @@ -# AI Organizer — Stage 1: Chat Sidebar with Ollama Connection - -## Goal - -Replace the sample plugin scaffolding with a functional Ollama chat sidebar. The sidebar view contains a chat area (top half) and a settings panel (bottom half). The user configures the Ollama URL, tests the connection, selects a model, and chats with the AI. - ---- - -## Existing State - -- Project is the Obsidian sample plugin template (TypeScript, esbuild). -- `manifest.json` has `id: "sample-plugin"`, `isDesktopOnly: false`. -- `src/main.ts` contains `MyPlugin` with boilerplate commands, ribbon icon, status bar, and a modal. -- `src/settings.ts` contains `MyPluginSettings` with a single `mySetting: string` field and `SampleSettingTab`. -- `styles.css` is empty (comments only). -- Build: `npm run dev` (esbuild watch), `npm run build` (tsc + esbuild production). - ---- - -## File Plan - -| File | Action | Purpose | -|------|--------|---------| -| `manifest.json` | Modify | Update `id`, `name`, `description`, `author`, `authorUrl`. Remove `fundingUrl`. | -| `package.json` | Modify | Update `name` and `description`. | -| `src/main.ts` | Rewrite | New plugin class `AIOrganizer`. Register view, register command, load/save settings. Remove all sample code. | -| `src/settings.ts` | Rewrite | New `AIOrganizerSettings` interface with `ollamaUrl` and `model`. New `DEFAULT_SETTINGS`. Remove `SampleSettingTab` (settings live in the sidebar view, not a settings tab). | -| `src/chat-view.ts` | Create | `ItemView` subclass for the sidebar. Contains chat UI (top) and settings panel (bottom). | -| `src/ollama-client.ts` | Create | Functions: `testConnection`, `listModels`, `sendChatMessage`. All use `requestUrl`. | -| `styles.css` | Rewrite | Styles for the chat view layout, messages, input area, settings panel. | - ---- - -## Step-by-Step Tasks - -### Step 1 — Update Metadata - -**`manifest.json`**: -- Set `id` to `"ai-organizer"`. -- Set `name` to `"AI Organizer"`. -- Set `description` to `"Organize notes via AI powered by Ollama."`. -- Set `author` to the repo owner's name. -- Set `authorUrl` to the repo URL. -- Remove `fundingUrl`. -- Keep `isDesktopOnly` as `false`. -- Keep `minAppVersion` as `"0.15.0"`. - -**`package.json`**: -- Set `name` to `"ai-organizer"`. -- Set `description` to match `manifest.json`. - -### Step 2 — Settings Interface - -**`src/settings.ts`** — delete all existing content and replace: - -- Define `AIOrganizerSettings` interface: - - `ollamaUrl: string` — the Ollama server base URL. - - `model: string` — the selected model name (empty string means none selected). -- Define `DEFAULT_SETTINGS: AIOrganizerSettings`: - - `ollamaUrl`: `"http://localhost:11434"` - - `model`: `""` -- Export both. -- Do NOT create a `PluginSettingTab`. Settings are embedded in the sidebar view. - -### Step 3 — Ollama Client - -**`src/ollama-client.ts`** — create: - -#### `testConnection(ollamaUrl: string): Promise` -- `GET {ollamaUrl}/api/version` using `requestUrl` with `throw: false`. -- On success (status 200): return the version string from `response.json.version`. -- On failure: throw an `Error` with a descriptive message. If `status` is 0 or the error message contains `"net"` or `"fetch"`, the message must say Ollama is unreachable. Otherwise include the status code. - -#### `listModels(ollamaUrl: string): Promise` -- `GET {ollamaUrl}/api/tags` using `requestUrl`. -- Return `response.json.models.map((m: {name: string}) => m.name)`. -- On failure: throw an `Error` with a descriptive message. - -#### `sendChatMessage(ollamaUrl: string, model: string, messages: ChatMessage[]): Promise` -- Define `ChatMessage` interface: `{ role: "system" | "user" | "assistant"; content: string }`. Export it. -- `POST {ollamaUrl}/api/chat` using `requestUrl`. -- Body: `{ model, messages, stream: false }`. -- Return `response.json.message.content`. -- On failure: throw an `Error` with a descriptive message. - -All three functions are standalone exports (no class). All use `import { requestUrl } from "obsidian"`. - -### Step 4 — Chat View - -**`src/chat-view.ts`** — create: - -- Export `VIEW_TYPE_CHAT = "ai-organizer-chat"`. -- Export class `ChatView extends ItemView`. - -#### Constructor -- Accept `leaf: WorkspaceLeaf` and a reference to the plugin instance (`AIOrganizer`). Store the plugin reference as a private property. - -#### `getViewType()` → return `VIEW_TYPE_CHAT`. - -#### `getDisplayText()` → return `"AI Chat"`. - -#### `getIcon()` → return `"message-square"`. - -#### `onOpen()` - -Build the entire UI inside `this.contentEl`. The layout is a vertical flexbox split into two regions: - -**Top region — Chat area** (flexbox column, `flex: 1`, overflow-y scroll): -- A message container `div` that holds chat message elements. -- Each message is a `div` with a CSS class indicating the role (`"user"` or `"assistant"`). -- Below the message container: an input row (flexbox row) with: - - A `textarea` for user input (flex: 1, placeholder: `"Type a message..."`). Pressing Enter (without Shift) sends the message. Shift+Enter inserts a newline. - - A send `button` (text: `"Send"`). -- The send button and Enter key trigger the send flow (defined below). -- While waiting for a response, disable the textarea and send button and change the button text to `"..."`. - -**Bottom region — Settings panel** (fixed height, border-top separator, padding, overflow-y auto): -- A heading element: `"Settings"`. -- **Ollama URL**: Use an Obsidian `Setting` component. - - Name: `"Ollama URL"`. - - Description: `"Base URL of the Ollama server."`. - - `addText` input pre-filled with `plugin.settings.ollamaUrl`. - - `onChange`: update `plugin.settings.ollamaUrl` and call `plugin.saveSettings()`. -- **Test Connection**: Use an Obsidian `Setting` component. - - Name: `"Test Connection"`. - - Description: initially empty. This description element will be used to display the result. - - `addButton` with text `"Test"`. - - `onClick`: call `testConnection(plugin.settings.ollamaUrl)`. - - On success: set the description to `"Connected — Ollama v{version}"`. Then automatically call `listModels` and populate the model dropdown (see below). - - On failure: set the description to the error message. -- **Model Selection**: Use an Obsidian `Setting` component. - - Name: `"Model"`. - - Description: `"Select the model to use."`. - - `addDropdown`. - - Initially the dropdown has one option: `{ value: "", display: "Test connection first" }` and is disabled. - - After a successful `testConnection` + `listModels`: - - Clear the dropdown options (use `selectEl.empty()` on the underlying `