mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor: centralize CSS class names in Selector enum
- Replace hardcoded class name strings with Selector enum values - Add new selector constants for styling and UI states - Filter cancelled request messages from saved conversations - Improve scroll behavior when streaming ends - Move plugin styles import to main plugin file
This commit is contained in:
parent
c188f5727e
commit
4c21b39a28
10 changed files with 61 additions and 30 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { AIProvider } from "Enums/ApiProvider";
|
||||
import { Path } from "Enums/Path";
|
||||
import { Selector } from "Enums/Selector";
|
||||
import type AIAgentPlugin from "main";
|
||||
import { PluginSettingTab, Setting, App, setIcon, setTooltip } from "obsidian";
|
||||
|
||||
|
|
@ -77,18 +78,18 @@ export class AIAgentSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.exclusions = value.split("\n").map(line => line.trim()).filter(line => line.length > 0);
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
text.inputEl.classList.add("ai-exclusions-input");
|
||||
text.inputEl.classList.add(Selector.AIExclusionsInput);
|
||||
});
|
||||
}
|
||||
|
||||
private highlightApiKey(): void {
|
||||
if (this.apiKeySetting) {
|
||||
if (this.plugin.settings.apiKey.trim() === "") {
|
||||
this.apiKeySetting.settingEl.removeClass("api-key-setting-ok");
|
||||
this.apiKeySetting.settingEl.addClass("api-key-setting-error");
|
||||
this.apiKeySetting.settingEl.removeClass(Selector.ApiKeySettingOk);
|
||||
this.apiKeySetting.settingEl.addClass(Selector.ApiKeySettingError);
|
||||
} else {
|
||||
this.apiKeySetting.settingEl.removeClass("api-key-setting-error");
|
||||
this.apiKeySetting.settingEl.addClass("api-key-setting-ok");
|
||||
this.apiKeySetting.settingEl.removeClass(Selector.ApiKeySettingError);
|
||||
this.apiKeySetting.settingEl.addClass(Selector.ApiKeySettingOk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ You are a knowledgeable AI assistant with specialized access to the user's Obsid
|
|||
|
||||
## Core Capabilities
|
||||
|
||||
**When referencing vault contents and user notes, always use Obsidian wiki-link syntax: [[note name]]**
|
||||
|
||||
You can help users with:
|
||||
- Finding and referencing information from their notes using wiki-link syntax
|
||||
- Finding and referencing information from their notes
|
||||
- Creating, updating, and organizing notes
|
||||
- Working with tags, links, and folder structures
|
||||
- Working with tags, links, graphs, and folder structures
|
||||
- General questions, problem-solving, and explanations across any domain
|
||||
- Programming, writing, and creative tasks
|
||||
|
||||
|
|
@ -100,7 +102,6 @@ When performing vault searches or reading multiple files:
|
|||
- Process descriptions unless they explain why results are limited
|
||||
|
||||
**Natural Integration:**
|
||||
- When referencing vault contents, **ALWAYS** use Obsidian wiki-link syntax: [[note name]]
|
||||
- Seamlessly combine vault information with your general knowledge
|
||||
- Always prefer vault content over generic information when available
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
import { Role } from "Enums/Role";
|
||||
import type { ConversationContent } from "Conversations/ConversationContent";
|
||||
import { tick } from "svelte";
|
||||
import type AIAgentPlugin from "main";
|
||||
import { Copy } from "Enums/Copy";
|
||||
import { Selector } from "Enums/Selector";
|
||||
|
||||
export let messages: ConversationContent[] = [];
|
||||
export let currentThought: string | null = null;
|
||||
|
|
@ -20,17 +23,26 @@
|
|||
chatContainer.offsetHeight - parseFloat(getComputedStyle(chatContainer).padding) * 2) {
|
||||
// Recalculate padding when streaming ends to fix race condition with streaming indicator removal
|
||||
assistantMessageAction(lastAssistantMessageElement);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
chatContainer.scroll({ top: chatContainer.scrollHeight, behavior: "smooth" });
|
||||
});
|
||||
});
|
||||
|
||||
// use an interval to complete scrolling once the dom has finished updating
|
||||
const scrollInterval: number = plugin.registerInterval(window.setInterval(() => {
|
||||
tick().then(() => {
|
||||
chatContainer.scroll({ top: chatContainer.scrollHeight, behavior: "smooth" });
|
||||
});
|
||||
}, 50));
|
||||
|
||||
setTimeout(() => {
|
||||
if (scrollInterval) {
|
||||
clearInterval(scrollInterval);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
let thoughtElement: HTMLElement | undefined;
|
||||
let streamingElement: HTMLElement | undefined;
|
||||
|
||||
let plugin: AIAgentPlugin = Resolve<AIAgentPlugin>(Services.AIAgentPlugin);
|
||||
let streamingMarkdownService: StreamingMarkdownService = Resolve<StreamingMarkdownService>(Services.StreamingMarkdownService);
|
||||
|
||||
let messageElements: Map<string, HTMLElement> = new Map<string, HTMLElement>();
|
||||
|
|
@ -123,6 +135,11 @@
|
|||
|
||||
// For assistant messages that aren't streaming, use traditional parsing
|
||||
if (!isCurrentlyStreaming) {
|
||||
// Check if this is a cancelled request message
|
||||
if (message.content === Copy.ApiRequestAborted) {
|
||||
return `<span class="${Selector.ApiRequestAborted}">Request has been cancelled</span>`;
|
||||
}
|
||||
|
||||
try {
|
||||
return streamingMarkdownService.formatText(message.content) || `<div>${message.content}</div>`;
|
||||
} catch (err) {
|
||||
|
|
|
|||
3
Enums/Copy.ts
Normal file
3
Enums/Copy.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export enum Copy {
|
||||
ApiRequestAborted = "Request has been cancelled"
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
export enum Selector {
|
||||
MarkDownLink = "ai-agent-internal-markdown-link",
|
||||
AIExclusionsInput = "ai-exclusions-input",
|
||||
ApiKeySettingOk = "api-key-setting-ok",
|
||||
ApiKeySettingError = "api-key-setting-error",
|
||||
ApiRequestAborted = "api-request-aborted",
|
||||
ConversationHistoryModal = "conversation-history-modal"
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import type { ConversationFileSystemService } from 'Services/ConversationFileSys
|
|||
import type { FileSystemService } from 'Services/FileSystemService';
|
||||
import { dateToString } from 'Helpers/Helpers';
|
||||
import { conversationStore } from 'Stores/conversationStore';
|
||||
import { Selector } from 'Enums/Selector';
|
||||
|
||||
interface ListItem {
|
||||
id: string;
|
||||
|
|
@ -50,8 +51,8 @@ export class ConversationHistoryModal extends Modal {
|
|||
onOpen() {
|
||||
const { contentEl, modalEl, containerEl } = this;
|
||||
|
||||
containerEl.addClass('conversation-history-modal');
|
||||
modalEl.addClass('conversation-history-modal');
|
||||
containerEl.addClass(Selector.ConversationHistoryModal);
|
||||
modalEl.addClass(Selector.ConversationHistoryModal);
|
||||
|
||||
this.component = mount(ConversationHistoryModalSvelte, {
|
||||
target: contentEl,
|
||||
|
|
@ -82,7 +83,7 @@ export class ConversationHistoryModal extends Modal {
|
|||
const currentPath = this.conversationFileSystemService.getCurrentConversationPath();
|
||||
|
||||
for (const item of itemsToDelete) {
|
||||
const deleted = await this.fileSystemService.deleteFile(item.filePath);
|
||||
const deleted = await this.fileSystemService.deleteFile(item.filePath, true);
|
||||
if (deleted && currentPath === item.filePath) {
|
||||
shouldResetChat = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { FileSystemService } from "./FileSystemService";
|
|||
import { Services } from "./Services";
|
||||
import { Conversation } from "Conversations/Conversation";
|
||||
import { ConversationContent } from "Conversations/ConversationContent";
|
||||
import { Copy } from "Enums/Copy";
|
||||
|
||||
export class ConversationFileSystemService {
|
||||
|
||||
|
|
@ -26,14 +27,16 @@ export class ConversationFileSystemService {
|
|||
const conversationData = {
|
||||
title: conversation.title,
|
||||
created: conversation.created.toISOString(),
|
||||
contents: conversation.contents.map(content => ({
|
||||
id: content.id,
|
||||
role: content.role,
|
||||
content: content.content,
|
||||
timestamp: content.timestamp.toISOString(),
|
||||
isFunctionCall: content.isFunctionCall,
|
||||
isFunctionCallResponse: content.isFunctionCallResponse
|
||||
}))
|
||||
contents: conversation.contents
|
||||
.filter(content => content.content !== Copy.ApiRequestAborted)
|
||||
.map(content => ({
|
||||
id: content.id,
|
||||
role: content.role,
|
||||
content: content.content,
|
||||
timestamp: content.timestamp.toISOString(),
|
||||
isFunctionCall: content.isFunctionCall,
|
||||
isFunctionCallResponse: content.isFunctionCallResponse
|
||||
}))
|
||||
};
|
||||
|
||||
await this.fileSystemService.writeObjectToFile(this.currentConversationPath, conversationData, true);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
|
||||
import { Copy } from "Enums/Copy";
|
||||
|
||||
export interface StreamChunk {
|
||||
content: string;
|
||||
|
|
@ -69,14 +70,11 @@ export class StreamingService {
|
|||
} catch (error) {
|
||||
// Don't log abort errors as they're intentional
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
console.log("Stream request aborted by user");
|
||||
yield {
|
||||
content: "",
|
||||
isComplete: true,
|
||||
error: '<span class="ai-request-cancelled">Request has been cancelled</span>',
|
||||
content: Copy.ApiRequestAborted,
|
||||
isComplete: true
|
||||
};
|
||||
} else {
|
||||
console.error("Stream request error:", error);
|
||||
yield {
|
||||
content: "",
|
||||
isComplete: true,
|
||||
|
|
|
|||
2
main.ts
2
main.ts
|
|
@ -23,6 +23,8 @@ export default class AIAgentPlugin extends Plugin {
|
|||
async onload() {
|
||||
// KaTeX CSS is bundled with the plugin to comply with CSP
|
||||
require('katex/dist/katex.min.css');
|
||||
// Plugin styles
|
||||
require('./styles.css');
|
||||
|
||||
await this.loadSettings();
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
background-color: var(--color-base-35);
|
||||
}
|
||||
|
||||
.ai-request-cancelled {
|
||||
.api-request-aborted {
|
||||
color: var(--color-red);
|
||||
border: var(--color-red);
|
||||
border-style: solid;
|
||||
|
|
|
|||
Loading…
Reference in a new issue