refactor: replace sparkles icon with custom plugin icon throughout app

- Add AssetsService to manage plugin icon and banner assets
- Update sidebar, ribbon, and quick actions to use new icon
- Add plugin banner to help modal welcome section
- Update help text to reference "plugin icon" instead of "sparkles icon"
This commit is contained in:
Andrew Beal 2026-05-31 19:45:05 +01:00
parent 72bf43a8ea
commit a77c5b3339
10 changed files with 79 additions and 5 deletions

View file

@ -0,0 +1,12 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Vaultkeeper AI ribbon icon. Uses currentColor so it adapts to Obsidian themes. -->
<circle cx="50" cy="50" r="30" stroke="currentColor" stroke-width="7" fill="none"/>
<g stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none">
<!-- locking bolts -->
<path d="M85 50 H92 M50 85 V92 M15 50 H8 M50 15 V8
M74.75 74.75 L79.70 79.70 M25.25 74.75 L20.30 79.70
M74.75 25.25 L79.70 20.30 M25.25 25.25 L20.30 20.30"/>
<!-- central spark -->
<path d="M50 33 L54.09 45.91 L67 50 L54.09 54.09 L50 67 L45.91 54.09 L33 50 L45.91 45.91 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View file

@ -223,7 +223,7 @@ If you find any issues or have a feature request, please feel free to raise them
1. **Add an API key**: Go to Settings and add at least one API key (Claude, Gemini, OpenAI, or Mistral)
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`,
3. **Open the chat**: Click the plugin icon in the sidebar to start chatting`,
HelpModalChatModesTitle = "Chat modes",
HelpModalChatModesContent = `#### Chat modes

View file

@ -8,11 +8,13 @@
import type { WorkSpaceService } from "Services/WorkSpaceService";
import { fade } from "svelte/transition";
import { onMount } from "svelte";
import type { AssetsService } from "Services/AssetsService";
export let onClose: () => void;
export let initialTopic: number = 1;
const plugin: VaultkeeperAIPlugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
const assetsService: AssetsService = Resolve<AssetsService>(Services.AssetsService);
const streamingMarkdownService: StreamingMarkdownService = Resolve<StreamingMarkdownService>(Services.StreamingMarkdownService);
const workSpaceService: WorkSpaceService = Resolve<WorkSpaceService>(Services.WorkSpaceService);
@ -166,6 +168,9 @@
</div>
<div class="help-modal-content" bind:this={contentContainer}>
{#if contentVisible}
{#if selectedTopic === 1}
<img class="help-modal-banner" src={assetsService.bannerSource} alt="Plugin Banner">
{/if}
<div transition:fade={{ duration: 100 }} use:helpContentAction={selectedTopic}></div>
<div transition:fade={{ duration: 100 }}>
{#if selectedTopic === 1}
@ -190,7 +195,7 @@
<span>{Copy.CoffeeIcon}</span>
<span>{Copy.CoffeeLinkText}</span>
</a>
<p style="margin-top: 2em; font-style: italic;">{Copy.ThankYouMessage}</p>
<p style="margin-top: 1em; font-style: italic;">{Copy.ThankYouMessage}</p>
{/if}
</div>
{#if selectedTopic === 1}
@ -323,6 +328,13 @@
overflow-y: auto;
}
.help-modal-banner {
margin-top: var(--size-2-2);
margin-left: calc(var(--size-4-2) * -1);
width: calc(100% + (var(--size-4-2) * 2) - var(--size-2-2));
border-radius: var(--radius-s);
}
.help-modal-version-string {
/* Absorbs leftover vertical space so the version sits bottom-right on tall
screens, but collapses and scrolls naturally when content overflows. */

33
Services/AssetsService.ts Normal file
View file

@ -0,0 +1,33 @@
import type VaultkeeperAIPlugin from "main";
import { Resolve } from "./DependencyService";
import { Services } from "./Services";
import { addIcon } from "obsidian";
export class AssetsService {
private readonly plugin: VaultkeeperAIPlugin;
public pluginIcon: string = "";
public bannerSource: string = "";
public constructor() {
this.plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
}
public async loadAssets(): Promise<void> {
this.pluginIcon = this.addIcon(
await this.plugin.app.vault.adapter.read(`${this.plugin.manifest.dir}/Assets/vaultkeeper-mono.svg`),
"vaultkeeper-ai-icon"
);
this.bannerSource = this.plugin.app.vault.adapter.getResourcePath(
`${this.plugin.manifest.dir}/Assets/vaultkeeper-social-1280x330.png`
);
}
private addIcon(icon: string, name: string): string {
addIcon(name, icon);
return name;
}
}

View file

@ -6,10 +6,12 @@ import type { SettingsService } from "Services/SettingsService";
import { WorkSpaceService } from "Services/WorkSpaceService";
import type { QuickActionsDefinitionsService } from "./QuickActionsDefinitionsService";
import type { HelpModal } from "Modals/HelpModal";
import { AssetsService } from "Services/AssetsService";
export class QuickActionsService {
private readonly plugin: VaultkeeperAIPlugin;
private readonly assetsService: AssetsService;
private readonly settingsService: SettingsService;
private readonly workSpaceService: WorkSpaceService;
private readonly quickActionsDefinitionsService: QuickActionsDefinitionsService;
@ -21,6 +23,7 @@ export class QuickActionsService {
public constructor() {
this.plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
this.assetsService = Resolve<AssetsService>(Services.AssetsService);
this.settingsService = Resolve<SettingsService>(Services.SettingsService);
this.workSpaceService = Resolve<WorkSpaceService>(Services.WorkSpaceService);
this.quickActionsDefinitionsService = Resolve<QuickActionsDefinitionsService>(Services.QuickActionsDefinitionsService);
@ -181,7 +184,7 @@ export class QuickActionsService {
);
menu.showAtMouseEvent(evt);
});
setIcon(button, "sparkles");
setIcon(button, this.assetsService.pluginIcon);
actionsEl.prepend(button);
}
}

View file

@ -2,6 +2,7 @@
import { AIProvider, fromModel } from "Enums/ApiProvider";
import { Environment } from "Enums/Environment";
import type VaultkeeperAIPlugin from "main";
import { AssetsService } from "./AssetsService";
// Services
import { RegisterSingleton, RegisterTransient, Resolve } from "./DependencyService";
@ -64,6 +65,7 @@ import { QuickActionsService } from "./QuickActions/QuickActionsService";
export async function RegisterPlugin(plugin: VaultkeeperAIPlugin) {
RegisterSingleton<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin, plugin);
RegisterSingleton<SettingsService>(Services.SettingsService, new SettingsService(await plugin.loadData() as Partial<IVaultkeeperAISettings>));
RegisterSingleton<AssetsService>(Services.AssetsService, new AssetsService());
}
export function RegisterDependencies() {

View file

@ -1,6 +1,7 @@
export class Services {
static VaultkeeperAIPlugin = Symbol("VaultkeeperAIPlugin");
static SettingsService = Symbol("SettingsService");
static AssetsService = Symbol("AssetsService");
static EventService = Symbol("EventService");
static AbortService = Symbol("AbortService");
static HTMLService = Symbol("HTMLService");

View file

@ -2,6 +2,9 @@ import { ItemView, WorkspaceLeaf } from 'obsidian';
import { mount, unmount } from 'svelte';
import ChatWindow from 'Components/ChatWindow.svelte';
import TopBar from 'Components/TopBar.svelte';
import { AssetsService } from 'Services/AssetsService';
import { Resolve } from 'Services/DependencyService';
import { Services } from 'Services/Services';
export const VIEW_TYPE_MAIN = 'vaultkeeper-ai-main-view';
@ -12,8 +15,11 @@ interface ChatWindowComponent {
export class MainView extends ItemView {
private readonly assetsService: AssetsService;
constructor(leaf: WorkspaceLeaf) {
super(leaf);
this.assetsService = Resolve<AssetsService>(Services.AssetsService);
}
topBar: ReturnType<typeof TopBar> | undefined;
@ -28,7 +34,7 @@ export class MainView extends ItemView {
}
public getIcon(): string {
return 'sparkles';
return this.assetsService.pluginIcon;
}
protected override onOpen(): Promise<void> {

View file

@ -14,11 +14,16 @@ import type { Diff2HtmlUIConfig } from "diff2html/lib/ui/js/diff2html-ui";
import "katex/dist/katex.min.css";
import 'highlight.js/styles/monokai.min.css';
import 'diff2html/bundles/css/diff2html.min.css';
import type { AssetsService } from "Services/AssetsService";
export default class VaultkeeperAIPlugin extends Plugin {
public async onload() {
await RegisterPlugin(this);
const assetsService = Resolve<AssetsService>(Services.AssetsService);
await assetsService.loadAssets();
RegisterDependencies();
this.registerView(
@ -38,7 +43,7 @@ export default class VaultkeeperAIPlugin extends Plugin {
}
});
this.addRibbonIcon("sparkles", "Vaultkeeper AI", async () => {
this.addRibbonIcon(assetsService.pluginIcon, "Vaultkeeper AI", async () => {
await this.activateMainView();
});