feat: add comprehensive help modal with guide and troubleshooting content

- Add detailed plugin guide covering modes, references, and instructions
- Add troubleshooting section with API key and rate limit solutions
- Add privacy documentation explaining data storage and exclusions
- Make "User Instructions" text clickable to open help modal
- Support opening help modal to specific topic via initialTopic prop
- Add internal link handling to navigate from help modal to vault files
- Update About section with GitHub link and contribution info
This commit is contained in:
Andrew Beal 2025-11-09 19:59:41 +00:00
parent 0a318a1caf
commit c9942e9ffa
4 changed files with 208 additions and 38 deletions

View file

@ -2,6 +2,7 @@
import { Copy } from "Enums/Copy";
import { Path } from "Enums/Path";
import type AIAgentPlugin from "main";
import { HelpModal } from "Modals/HelpModal";
import { basename } from "path-browserify";
import { Resolve } from "Services/DependencyService";
import type { FileSystemService } from "Services/FileSystemService";
@ -123,6 +124,11 @@
}
});
}
function openHelpModal() {
const modal = Resolve<HelpModal>(Services.HelpModal);
modal.open(2);
}
</script>
<div id="user-instruction-results" style:height="{height}px" bind:this={resultsContainer}>
@ -133,7 +139,11 @@
<div class="user-instruction-title">
<span>
{Copy.UserInstructions1}
<span id="user-instruction-link">
<span id="user-instruction-link"
role="link"
tabindex="-1"
on:click={openHelpModal}
on:keydown={openHelpModal}>
{Copy.UserInstructions2}
</span>
{Copy.UserInstructions3}

View file

@ -58,18 +58,146 @@ export enum Copy {
// Help Modal Copy
HelpModalAboutTitle = "About",
HelpModalAboutContent = `### About AI Agent
HelpModalAboutContent = `#### About AI Agent
**AI Agent** brings the power of Claude, Gemini, and OpenAI directly into your Obsidian vault with intelligent note management capabilities.`,
This plugin was originally created for a friend who found it useful, so I have decided to release it to the Obsidian community.
If you find any issues, please feel free to raise them on GitHub:`,
HelpModalGuideTitle = "Plugin Guide",
HelpModalGuideContent = "",
HelpModalGuideContent = `#### How to Use AI Agent
##### Getting Started
1. **Add an API Key**: Go to Settings and add at least one API key (Claude, Gemini, or OpenAI)
2. **Select a Model**: Choose your preferred AI model from the dropdown
3. **Open the Chat**: Click the sparkles icon in the sidebar to start chatting
##### Operating Modes
**Read-Only Mode (Default)** - The AI can safely explore your vault:
- Search through your notes
- Read file contents
- List directory structures
- Cannot modify anything
**Agent Mode** - Toggle when you need the AI to make changes:
- Create new notes
- Edit existing content
- Delete or move files
- Rename files
##### Reference System
Quickly provide context to the AI:
- **@filename** - Reference specific files
- **#tag** - Reference all notes with a tag
- **/folder** - Reference entire directories
The autocomplete dropdown supports keyboard navigation.
##### Custom Instructions
Customize AI behavior for specific workflows:
1. Create markdown files in **AI Agent/User Instructions/**
2. Click the "User Instructions" button in chat
3. Select your instruction set
4. The AI follows these instructions for all interactions
See [Example Template](#/page/AI%20Agent%2FUser%20Instructions%2FEXAMPLE_INSTRUCTIONS) for help getting started.`,
HelpModalTroubleshootTitle = "Troubleshooting",
HelpModalTroubleshootContent = "",
HelpModalTroubleshootContent = `#### Common Issues & Solutions
##### API Key Issues
**Problem**: "Invalid API key" or authentication errors
**Solutions**:
- Verify your API key is correct (copy fresh from provider console)
- Check you've added the key for the correct provider
- Ensure no extra spaces when pasting
- API keys are provider-specific - Claude keys only work with Claude models
##### Error Code 429: Rate Limit Exceeded
This error means you've made too many API requests in a given time period. This is separate from your token usage limits.
**How to resolve:**
- Wait for your rate limit to reset (see provider-specific details below)
- Lower the context settings in the plugin settings to reduce request frequency
- Understand that rate limits typically increase automatically as you use the API more
###### Claude
- **Wait time:** A few hours
- **Long-term solution:** Rate limits increase significantly after reaching cumulative spending thresholds
- [Claude Rate Limits Documentation](https://docs.claude.com/en/api/rate-limits)
###### OpenAI
- **Wait time:** A few minutes to an hour
- **Long-term solution:** Rate limits automatically increase as you progress through usage tiers with higher spending
- [OpenAI Rate Limits Documentation](https://platform.openai.com/docs/guides/rate-limits)
###### Gemini
- **Wait time:** A few minutes (per-minute limits) or until midnight Pacific Time (daily quotas)
- **Long-term solution:** Enable billing to move from free tier to paid tier for significantly higher limits. Paid tier limits increase automatically with cumulative Google Cloud spending
- [Gemini API Rate Limits Documentation](https://ai.google.dev/gemini-api/docs/rate-limits)`,
HelpModalPrivacyTitle = "Privacy",
HelpModalPrivacyContent = "",
HelpModalPrivacyContent = `#### Privacy & Security
##### Data Storage
**Everything stays local** - This plugin does NOT send data to any third-party services except the AI providers you choose. It also doesn't collect any telemetry data and you can review the full source code on GitHub.
**What's stored locally**:
- API keys (stored in your vault's plugin settings)
- Conversation history (stored in \`AI Agent/Conversations/\`)
- Custom instructions (stored in \`AI Agent/User Instructions/\`)
- Plugin settings (stored in \`.obsidian/plugins/ai-agent-plugin/\`)
##### API Communication
**Direct connections only** - The plugin communicates directly with your chosen AI provider:
- **Claude**: Anthropic's API
- **Gemini**: Google's API
- **OpenAI**: OpenAI's API
**What gets sent**:
- Your messages and referenced file contents
- Conversation context (for continuity)
- File names and directory structures (when AI searches)
**What does NOT get sent**:
- Files excluded in settings
- Other plugins' data
- Your vault structure (unless explicitly requested)
- API keys to anyone except the respective provider
##### File Exclusions
**Protect sensitive information** using glob patterns in settings:
**Examples**:
- \`private/**\` - Exclude entire directories
- \`*.secret.md\` - Exclude specific file patterns
- \`journal/personal/**\` - Exclude nested directories
- \`.obsidian/workspace.json\` - Exclude specific files
**How exclusions work**:
- Excluded files are completely invisible to the AI
- The AI cannot read, search, modify, or even list excluded files
- Exclusions apply to All operations
##### AI Provider Policies
Each AI provider has their own data policies:
- [Anthropic (Claude)](https://www.anthropic.com/privacy)
- [Google (Gemini)](https://policies.google.com/privacy)
- [OpenAI (ChatGPT)](https://openai.com/privacy/)`,
// SVG Icons
GitHubIconPath = "M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z",

View file

@ -24,11 +24,17 @@ export class HelpModal extends Modal {
this.component = mount(HelpModalSvelte, {
target: contentEl,
props: {
onClose: () => this.close()
onClose: () => this.close(),
initialTopic: (this as any).initialTopic
}
});
}
public open(initialTopic?: number): void {
(this as any).initialTopic = initialTopic;
super.open();
}
onClose() {
if (this.component) {
unmount(this.component);

View file

@ -1,20 +1,25 @@
<script lang="ts">
import { Copy } from "Enums/Copy";
import { Selector } from "Enums/Selector";
import type AIAgentPlugin from "main";
import { DropdownComponent, setIcon } from "obsidian";
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import type { StreamingMarkdownService } from "Services/StreamingMarkdownService";
import type { WorkSpaceService } from "Services/WorkSpaceService";
import { fade } from "svelte/transition";
import { onMount } from "svelte";
export let onClose: () => void;
export let initialTopic: number = 1;
const plugin: AIAgentPlugin = Resolve<AIAgentPlugin>(Services.AIAgentPlugin);
const streamingMarkdownService: StreamingMarkdownService = Resolve<StreamingMarkdownService>(Services.StreamingMarkdownService);
const workSpaceService: WorkSpaceService = Resolve<WorkSpaceService>(Services.WorkSpaceService);
let closeButton: HTMLButtonElement;
let dropdownContainer: HTMLDivElement;
let contentContainer: HTMLDivElement;
const topics: Record<number, { title: string; content: string }> = {
1: {
@ -35,7 +40,7 @@
}
};
let selectedTopic: number = 1;
let selectedTopic: number = initialTopic;
let title: string = topics[selectedTopic].title;
let content: string = streamingMarkdownService.formatText(topics[selectedTopic].content);
@ -53,6 +58,29 @@
setIcon(closeButton, 'circle-x');
}
async function handleLinkClick(evt: MouseEvent) {
const target = evt.target as HTMLElement;
// Check for both internal wikilinks and regular markdown links
const link = target.closest('a') as HTMLAnchorElement | null;
if (!link) {
return;
}
const href = link.getAttribute('href');
if (!href || !href.startsWith('#/page/')) {
return;
}
evt.preventDefault();
evt.stopPropagation();
const encodedPath = href.replace('#/page/', '');
const notePath = decodeURIComponent(encodedPath);
await workSpaceService.openNote(notePath);
onClose();
}
onMount(() => {
if (dropdownContainer) {
const dropdown = new DropdownComponent(dropdownContainer);
@ -70,6 +98,10 @@
selectTopic(Number(value));
});
}
if (contentContainer) {
plugin.registerDomEvent(contentContainer, 'click', handleLinkClick);
}
});
</script>
@ -144,39 +176,33 @@
Plugin version: {plugin.manifest.version}
</div>
</div>
<div class="help-modal-content">
<div class="help-modal-content" bind:this={contentContainer}>
{#if content !== ""}
<div transition:fade={{ duration: 100 }}>
{@html content}
{#if selectedTopic === 1}
<hr style="margin: 2em 0; border-width: 1px">
<h4>Links</h4>
<ul style="list-style: none; padding-left: 0;">
<li style="margin-bottom: 0.5em;">
<a
href="https://github.com/yourusername/ai-agent-plugin"
style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5em;">
<svg
width="1em"
height="1em"
viewBox="0 0 98 96"
xmlns="http://www.w3.org/2000/svg"
aria-label="GitHub"
style="display: inline-block; vertical-align: middle;">
<path fill-rule="evenodd" clip-rule="evenodd" d={Copy.GitHubIconPath} fill="currentColor"/>
</svg>
<span>View on GitHub</span>
</a>
</li>
<li style="margin-bottom: 0.5em;">
<a href="https://buymeacoffee.com/yourusername" style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5em;">
<span></span>
<span>Buy me a coffee</span>
</a>
</li>
</ul>
<hr style="margin: 2em 0; border-width: 1px">
<p style="font-style: italic; text-align: center;">Created with ❤️ for the Obsidian community</p>
<a
href="https://github.com/Andy-Stack/ai-agent-plugin"
style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5em; margin: 0 0 1em 0;">
<svg
width="1em"
height="1em"
viewBox="0 0 98 96"
xmlns="http://www.w3.org/2000/svg"
aria-label="GitHub"
style="display: inline-block; vertical-align: middle;">
<path fill-rule="evenodd" clip-rule="evenodd" d={Copy.GitHubIconPath} fill="currentColor"/>
</svg>
<span>View on GitHub</span>
</a>
<br>
<span style="display: inline-block; margin-bottom: 1em;">If you enjoy using the plugin or find it useful and want to contribute, you can buy me a Coffee using the link below:</span>
<br>
<a href="a" style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5em;">
<span></span>
<span>Buy me a coffee</span>
</a>
<p style="margin-top: 2em; font-style: italic;">Thanks for using the AI Agent plugin!</p>
{/if}
</div>
{/if}
@ -337,7 +363,7 @@
height: 100%;
border-radius: var(--radius-m);
background-color: var(--alt-background-primary);
padding: 0 var(--size-4-2) var(--size-4-2) var(--size-4-6);
padding: 0 var(--size-4-2) var(--size-4-2) var(--size-4-3);
overflow-y: auto;
}