v0.2.1: Security fixes, @ mentions, system context file, UX improvements
- Cap thread history sent to API at last 10 messages (privacy/payload size) - Fix @ mention parsing for filenames with spaces & special chars (e.g. "Freiheit & Technologie") - Add System Context File setting: optional vault note appended to system prompt on every request - helpText textarea in prompt button settings: 1 row when empty, auto-expands with content - Hide Draft Check hint panel after sending - registerDomEvent for input listeners (proper Obsidian API cleanup) - Public setStatus/setInputValue/setExplicitContext methods on ChatView (remove @ts-ignore) - Remove duplicate model entry from MODELS array - Remove empty authorUrl from manifest.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
"Bash(/opt/homebrew/bin/gh repo create memex-chat --public --source=. --remote=origin --push)",
|
||||
"Bash(npm run build)",
|
||||
"Bash(npm install)",
|
||||
"WebFetch(domain:docs.obsidian.md)"
|
||||
"WebFetch(domain:docs.obsidian.md)",
|
||||
"Bash(grep -n \"data\\\\.json\\\\|apiKey\\\\|saveData\\\\|loadData\" src/*.ts)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,14 +211,13 @@ var ChatView = class extends import_obsidian.ItemView {
|
||||
this.setStatus("\u26A0 Bitte API Key in den Einstellungen eingeben");
|
||||
return;
|
||||
}
|
||||
const mentionPattern = /@([\w\däöüÄÖÜß][^@\n]{1,}?)(?=\s|$)/g;
|
||||
const mentions = [];
|
||||
let match;
|
||||
while ((match = mentionPattern.exec(query)) !== null) {
|
||||
const name = match[1].trim();
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(name, "");
|
||||
if (file)
|
||||
const seenPaths = /* @__PURE__ */ new Set();
|
||||
for (const file of this.app.vault.getMarkdownFiles()) {
|
||||
if (query.includes(`@${file.basename}`) && !seenPaths.has(file.path)) {
|
||||
mentions.push(file);
|
||||
seenPaths.add(file.path);
|
||||
}
|
||||
}
|
||||
if (this.activeExtensions.size === 0) {
|
||||
if (this.plugin.settings.autoRetrieveContext && this.plugin.settings.showContextPreview) {
|
||||
@@ -260,7 +259,7 @@ var ChatView = class extends import_obsidian.ItemView {
|
||||
this.isLoading = false;
|
||||
}
|
||||
async sendMessage(query, additionalFiles = []) {
|
||||
var _a, _b;
|
||||
var _a, _b, _c, _d;
|
||||
this.isLoading = true;
|
||||
this.sendBtn.disabled = true;
|
||||
const thread = this.activeThread;
|
||||
@@ -299,6 +298,7 @@ ${content}`;
|
||||
this.pendingContext = [];
|
||||
this.explicitContext = [];
|
||||
this.clearContextPreview();
|
||||
this.modeHintEl.style.display = "none";
|
||||
this.renderMessages();
|
||||
this.renderThreadList();
|
||||
const assistantMsg = {
|
||||
@@ -309,7 +309,7 @@ ${content}`;
|
||||
};
|
||||
thread.messages.push(assistantMsg);
|
||||
this.renderMessages();
|
||||
const claudeMessages = thread.messages.slice(0, -1).map((m) => ({
|
||||
const claudeMessages = thread.messages.slice(0, -1).slice(-10).map((m) => ({
|
||||
role: m.role,
|
||||
content: m.content
|
||||
}));
|
||||
@@ -319,8 +319,15 @@ ${content}`;
|
||||
});
|
||||
this.setStatus("Claude denkt\u2026");
|
||||
let systemPrompt = this.plugin.settings.systemPrompt;
|
||||
const ctxPath = this.plugin.settings.systemContextFile;
|
||||
if (ctxPath) {
|
||||
const ctxFile = (_b = (_a = this.app.metadataCache.getFirstLinkpathDest(ctxPath, "")) != null ? _a : this.app.vault.getAbstractFileByPath(ctxPath + ".md")) != null ? _b : this.app.vault.getAbstractFileByPath(ctxPath);
|
||||
if (ctxFile instanceof import_obsidian.TFile) {
|
||||
systemPrompt += "\n\n---\n" + await this.app.vault.cachedRead(ctxFile);
|
||||
}
|
||||
}
|
||||
for (const filePath of this.activeExtensions) {
|
||||
const file = (_b = (_a = this.app.metadataCache.getFirstLinkpathDest(filePath, "")) != null ? _a : this.app.vault.getAbstractFileByPath(filePath + ".md")) != null ? _b : this.app.vault.getAbstractFileByPath(filePath);
|
||||
const file = (_d = (_c = this.app.metadataCache.getFirstLinkpathDest(filePath, "")) != null ? _c : this.app.vault.getAbstractFileByPath(filePath + ".md")) != null ? _d : this.app.vault.getAbstractFileByPath(filePath);
|
||||
if (file instanceof import_obsidian.TFile) {
|
||||
const ext = await this.app.vault.cachedRead(file);
|
||||
systemPrompt += "\n\n---\n" + ext;
|
||||
@@ -1190,6 +1197,7 @@ Wenn du Fragen beantwortest:
|
||||
threadsFolder: "Calendar/Chat",
|
||||
sendOnEnter: false,
|
||||
contextProperties: ["collection", "related", "up", "tags"],
|
||||
systemContextFile: "",
|
||||
promptButtons: [
|
||||
{
|
||||
label: "Draft Check",
|
||||
@@ -1401,9 +1409,15 @@ var MemexChatSettingsTab = class extends import_obsidian3.PluginSettingTab {
|
||||
const helpLabel = card.createEl("label", { cls: "vc-pbtn-folder-label", text: "Hilfetext (optional, erscheint im Chat wenn Button aktiv):" });
|
||||
const helpTextArea = card.createEl("textarea", {
|
||||
cls: "vc-pbtn-help-textarea",
|
||||
attr: { rows: "3", placeholder: "z.B. DRAFT \u2014 Fr\xFChphase\u2026\nPRE-PUBLISH \u2014 Fast fertig\u2026" }
|
||||
attr: { placeholder: "z.B. DRAFT \u2014 Fr\xFChphase\u2026\nPRE-PUBLISH \u2014 Fast fertig\u2026" }
|
||||
});
|
||||
helpTextArea.value = (_a = pb.helpText) != null ? _a : "";
|
||||
const updateHelpRows = () => {
|
||||
const lines = helpTextArea.value.split("\n").length;
|
||||
helpTextArea.rows = helpTextArea.value.trim() ? Math.max(2, lines) : 1;
|
||||
};
|
||||
updateHelpRows();
|
||||
helpTextArea.addEventListener("input", updateHelpRows);
|
||||
helpTextArea.addEventListener("change", async () => {
|
||||
pb.helpText = helpTextArea.value.trim() || void 0;
|
||||
await this.plugin.saveSettings();
|
||||
@@ -1454,6 +1468,12 @@ var MemexChatSettingsTab = class extends import_obsidian3.PluginSettingTab {
|
||||
textarea.inputEl.style.fontFamily = "monospace";
|
||||
textarea.inputEl.style.fontSize = "12px";
|
||||
});
|
||||
new import_obsidian3.Setting(containerEl).setName("System Context (Datei)").setDesc("Optionale Vault-Notiz, deren Inhalt an den System Prompt angeh\xE4ngt wird (Pfad ohne .md)").addText(
|
||||
(text) => text.setPlaceholder("z.B. Prompts/Mein System Context").setValue(this.plugin.settings.systemContextFile).onChange(async (value) => {
|
||||
this.plugin.settings.systemContextFile = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
containerEl.createEl("h3", { text: "Aktionen" });
|
||||
new import_obsidian3.Setting(containerEl).setName("Index neu aufbauen").setDesc("Vault-Index f\xFCr die Suche neu aufbauen (dauert je nach Vault-Gr\xF6\xDFe einige Sekunden)").addButton(
|
||||
(btn) => btn.setButtonText("Index neu aufbauen").setCta().onClick(async () => {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "memex-chat",
|
||||
"name": "Memex Chat",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"minAppVersion": "1.4.0",
|
||||
"description": "Chat with your Obsidian vault using Claude AI — semantic context retrieval, @ mentions, thread history.",
|
||||
"author": "Sven",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "memex-chat",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "Obsidian plugin: Chat with your vault using Claude AI",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
+21
-9
@@ -260,14 +260,14 @@ export class ChatView extends ItemView {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse @Notizname mentions from input
|
||||
const mentionPattern = /@([\w\däöüÄÖÜß][^@\n]{1,}?)(?=\s|$)/g;
|
||||
// Parse @Notizname mentions — match against actual vault filenames to handle spaces & special chars
|
||||
const mentions: TFile[] = [];
|
||||
let match;
|
||||
while ((match = mentionPattern.exec(query)) !== null) {
|
||||
const name = match[1].trim();
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(name, "");
|
||||
if (file) mentions.push(file);
|
||||
const seenPaths = new Set<string>();
|
||||
for (const file of this.app.vault.getMarkdownFiles()) {
|
||||
if (query.includes(`@${file.basename}`) && !seenPaths.has(file.path)) {
|
||||
mentions.push(file);
|
||||
seenPaths.add(file.path);
|
||||
}
|
||||
}
|
||||
|
||||
// With active prompt extensions, skip auto-retrieve and clear any leftover context
|
||||
@@ -362,6 +362,7 @@ export class ChatView extends ItemView {
|
||||
this.pendingContext = [];
|
||||
this.explicitContext = [];
|
||||
this.clearContextPreview();
|
||||
this.modeHintEl.style.display = "none";
|
||||
this.renderMessages();
|
||||
this.renderThreadList();
|
||||
|
||||
@@ -375,9 +376,10 @@ export class ChatView extends ItemView {
|
||||
thread.messages.push(assistantMsg);
|
||||
this.renderMessages();
|
||||
|
||||
// Build Claude messages (history)
|
||||
// Build Claude messages (history) — cap at last 10 messages to limit API payload
|
||||
const claudeMessages: ClaudeMessage[] = thread.messages
|
||||
.slice(0, -1) // exclude the empty assistant msg we just added
|
||||
.slice(-10) // keep only the last 10 messages
|
||||
.map((m) => ({
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
@@ -391,8 +393,18 @@ export class ChatView extends ItemView {
|
||||
|
||||
this.setStatus("Claude denkt…");
|
||||
|
||||
// Build effective system prompt (base + any active extensions)
|
||||
// Build effective system prompt (base + optional system context file + active extensions)
|
||||
let systemPrompt = this.plugin.settings.systemPrompt;
|
||||
const ctxPath = this.plugin.settings.systemContextFile;
|
||||
if (ctxPath) {
|
||||
const ctxFile =
|
||||
this.app.metadataCache.getFirstLinkpathDest(ctxPath, "") ??
|
||||
(this.app.vault.getAbstractFileByPath(ctxPath + ".md") as TFile | null) ??
|
||||
(this.app.vault.getAbstractFileByPath(ctxPath) as TFile | null);
|
||||
if (ctxFile instanceof TFile) {
|
||||
systemPrompt += "\n\n---\n" + await this.app.vault.cachedRead(ctxFile);
|
||||
}
|
||||
}
|
||||
for (const filePath of this.activeExtensions) {
|
||||
const file =
|
||||
this.app.metadataCache.getFirstLinkpathDest(filePath, "") ??
|
||||
|
||||
+23
-1
@@ -22,6 +22,7 @@ export interface MemexChatSettings {
|
||||
sendOnEnter: boolean;
|
||||
contextProperties: string[];
|
||||
promptButtons: PromptButton[];
|
||||
systemContextFile: string; // optional vault path for extended system context
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: MemexChatSettings = {
|
||||
@@ -43,6 +44,7 @@ Wenn du Fragen beantwortest:
|
||||
threadsFolder: "Calendar/Chat",
|
||||
sendOnEnter: false,
|
||||
contextProperties: ["collection", "related", "up", "tags"],
|
||||
systemContextFile: "",
|
||||
promptButtons: [
|
||||
{
|
||||
label: "Draft Check",
|
||||
@@ -309,9 +311,16 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
||||
const helpLabel = card.createEl("label", { cls: "vc-pbtn-folder-label", text: "Hilfetext (optional, erscheint im Chat wenn Button aktiv):" });
|
||||
const helpTextArea = card.createEl("textarea", {
|
||||
cls: "vc-pbtn-help-textarea",
|
||||
attr: { rows: "3", placeholder: "z.B. DRAFT — Frühphase…\nPRE-PUBLISH — Fast fertig…" },
|
||||
attr: { placeholder: "z.B. DRAFT — Frühphase…\nPRE-PUBLISH — Fast fertig…" },
|
||||
}) as HTMLTextAreaElement;
|
||||
helpTextArea.value = pb.helpText ?? "";
|
||||
// 1 row when empty, auto-fit to content when filled
|
||||
const updateHelpRows = () => {
|
||||
const lines = helpTextArea.value.split("\n").length;
|
||||
helpTextArea.rows = helpTextArea.value.trim() ? Math.max(2, lines) : 1;
|
||||
};
|
||||
updateHelpRows();
|
||||
helpTextArea.addEventListener("input", updateHelpRows);
|
||||
helpTextArea.addEventListener("change", async () => {
|
||||
pb.helpText = helpTextArea.value.trim() || undefined;
|
||||
await this.plugin.saveSettings();
|
||||
@@ -385,6 +394,19 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
||||
textarea.inputEl.style.fontSize = "12px";
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("System Context (Datei)")
|
||||
.setDesc("Optionale Vault-Notiz, deren Inhalt an den System Prompt angehängt wird (Pfad ohne .md)")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("z.B. Prompts/Mein System Context")
|
||||
.setValue(this.plugin.settings.systemContextFile)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.systemContextFile = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// --- Actions ---
|
||||
containerEl.createEl("h3", { text: "Aktionen" });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user