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(/opt/homebrew/bin/gh repo create memex-chat --public --source=. --remote=origin --push)",
|
||||||
"Bash(npm run build)",
|
"Bash(npm run build)",
|
||||||
"Bash(npm install)",
|
"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");
|
this.setStatus("\u26A0 Bitte API Key in den Einstellungen eingeben");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mentionPattern = /@([\w\däöüÄÖÜß][^@\n]{1,}?)(?=\s|$)/g;
|
|
||||||
const mentions = [];
|
const mentions = [];
|
||||||
let match;
|
const seenPaths = /* @__PURE__ */ new Set();
|
||||||
while ((match = mentionPattern.exec(query)) !== null) {
|
for (const file of this.app.vault.getMarkdownFiles()) {
|
||||||
const name = match[1].trim();
|
if (query.includes(`@${file.basename}`) && !seenPaths.has(file.path)) {
|
||||||
const file = this.app.metadataCache.getFirstLinkpathDest(name, "");
|
|
||||||
if (file)
|
|
||||||
mentions.push(file);
|
mentions.push(file);
|
||||||
|
seenPaths.add(file.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.activeExtensions.size === 0) {
|
if (this.activeExtensions.size === 0) {
|
||||||
if (this.plugin.settings.autoRetrieveContext && this.plugin.settings.showContextPreview) {
|
if (this.plugin.settings.autoRetrieveContext && this.plugin.settings.showContextPreview) {
|
||||||
@@ -260,7 +259,7 @@ var ChatView = class extends import_obsidian.ItemView {
|
|||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
async sendMessage(query, additionalFiles = []) {
|
async sendMessage(query, additionalFiles = []) {
|
||||||
var _a, _b;
|
var _a, _b, _c, _d;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.sendBtn.disabled = true;
|
this.sendBtn.disabled = true;
|
||||||
const thread = this.activeThread;
|
const thread = this.activeThread;
|
||||||
@@ -299,6 +298,7 @@ ${content}`;
|
|||||||
this.pendingContext = [];
|
this.pendingContext = [];
|
||||||
this.explicitContext = [];
|
this.explicitContext = [];
|
||||||
this.clearContextPreview();
|
this.clearContextPreview();
|
||||||
|
this.modeHintEl.style.display = "none";
|
||||||
this.renderMessages();
|
this.renderMessages();
|
||||||
this.renderThreadList();
|
this.renderThreadList();
|
||||||
const assistantMsg = {
|
const assistantMsg = {
|
||||||
@@ -309,7 +309,7 @@ ${content}`;
|
|||||||
};
|
};
|
||||||
thread.messages.push(assistantMsg);
|
thread.messages.push(assistantMsg);
|
||||||
this.renderMessages();
|
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,
|
role: m.role,
|
||||||
content: m.content
|
content: m.content
|
||||||
}));
|
}));
|
||||||
@@ -319,8 +319,15 @@ ${content}`;
|
|||||||
});
|
});
|
||||||
this.setStatus("Claude denkt\u2026");
|
this.setStatus("Claude denkt\u2026");
|
||||||
let systemPrompt = this.plugin.settings.systemPrompt;
|
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) {
|
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) {
|
if (file instanceof import_obsidian.TFile) {
|
||||||
const ext = await this.app.vault.cachedRead(file);
|
const ext = await this.app.vault.cachedRead(file);
|
||||||
systemPrompt += "\n\n---\n" + ext;
|
systemPrompt += "\n\n---\n" + ext;
|
||||||
@@ -1190,6 +1197,7 @@ Wenn du Fragen beantwortest:
|
|||||||
threadsFolder: "Calendar/Chat",
|
threadsFolder: "Calendar/Chat",
|
||||||
sendOnEnter: false,
|
sendOnEnter: false,
|
||||||
contextProperties: ["collection", "related", "up", "tags"],
|
contextProperties: ["collection", "related", "up", "tags"],
|
||||||
|
systemContextFile: "",
|
||||||
promptButtons: [
|
promptButtons: [
|
||||||
{
|
{
|
||||||
label: "Draft Check",
|
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 helpLabel = card.createEl("label", { cls: "vc-pbtn-folder-label", text: "Hilfetext (optional, erscheint im Chat wenn Button aktiv):" });
|
||||||
const helpTextArea = card.createEl("textarea", {
|
const helpTextArea = card.createEl("textarea", {
|
||||||
cls: "vc-pbtn-help-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 : "";
|
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 () => {
|
helpTextArea.addEventListener("change", async () => {
|
||||||
pb.helpText = helpTextArea.value.trim() || void 0;
|
pb.helpText = helpTextArea.value.trim() || void 0;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -1454,6 +1468,12 @@ var MemexChatSettingsTab = class extends import_obsidian3.PluginSettingTab {
|
|||||||
textarea.inputEl.style.fontFamily = "monospace";
|
textarea.inputEl.style.fontFamily = "monospace";
|
||||||
textarea.inputEl.style.fontSize = "12px";
|
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" });
|
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(
|
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 () => {
|
(btn) => btn.setButtonText("Index neu aufbauen").setCta().onClick(async () => {
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "memex-chat",
|
"id": "memex-chat",
|
||||||
"name": "Memex Chat",
|
"name": "Memex Chat",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"minAppVersion": "1.4.0",
|
"minAppVersion": "1.4.0",
|
||||||
"description": "Chat with your Obsidian vault using Claude AI — semantic context retrieval, @ mentions, thread history.",
|
"description": "Chat with your Obsidian vault using Claude AI — semantic context retrieval, @ mentions, thread history.",
|
||||||
"author": "Sven",
|
"author": "Sven",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "memex-chat",
|
"name": "memex-chat",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Obsidian plugin: Chat with your vault using Claude AI",
|
"description": "Obsidian plugin: Chat with your vault using Claude AI",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+21
-9
@@ -260,14 +260,14 @@ export class ChatView extends ItemView {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse @Notizname mentions from input
|
// Parse @Notizname mentions — match against actual vault filenames to handle spaces & special chars
|
||||||
const mentionPattern = /@([\w\däöüÄÖÜß][^@\n]{1,}?)(?=\s|$)/g;
|
|
||||||
const mentions: TFile[] = [];
|
const mentions: TFile[] = [];
|
||||||
let match;
|
const seenPaths = new Set<string>();
|
||||||
while ((match = mentionPattern.exec(query)) !== null) {
|
for (const file of this.app.vault.getMarkdownFiles()) {
|
||||||
const name = match[1].trim();
|
if (query.includes(`@${file.basename}`) && !seenPaths.has(file.path)) {
|
||||||
const file = this.app.metadataCache.getFirstLinkpathDest(name, "");
|
mentions.push(file);
|
||||||
if (file) mentions.push(file);
|
seenPaths.add(file.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// With active prompt extensions, skip auto-retrieve and clear any leftover context
|
// With active prompt extensions, skip auto-retrieve and clear any leftover context
|
||||||
@@ -362,6 +362,7 @@ export class ChatView extends ItemView {
|
|||||||
this.pendingContext = [];
|
this.pendingContext = [];
|
||||||
this.explicitContext = [];
|
this.explicitContext = [];
|
||||||
this.clearContextPreview();
|
this.clearContextPreview();
|
||||||
|
this.modeHintEl.style.display = "none";
|
||||||
this.renderMessages();
|
this.renderMessages();
|
||||||
this.renderThreadList();
|
this.renderThreadList();
|
||||||
|
|
||||||
@@ -375,9 +376,10 @@ export class ChatView extends ItemView {
|
|||||||
thread.messages.push(assistantMsg);
|
thread.messages.push(assistantMsg);
|
||||||
this.renderMessages();
|
this.renderMessages();
|
||||||
|
|
||||||
// Build Claude messages (history)
|
// Build Claude messages (history) — cap at last 10 messages to limit API payload
|
||||||
const claudeMessages: ClaudeMessage[] = thread.messages
|
const claudeMessages: ClaudeMessage[] = thread.messages
|
||||||
.slice(0, -1) // exclude the empty assistant msg we just added
|
.slice(0, -1) // exclude the empty assistant msg we just added
|
||||||
|
.slice(-10) // keep only the last 10 messages
|
||||||
.map((m) => ({
|
.map((m) => ({
|
||||||
role: m.role,
|
role: m.role,
|
||||||
content: m.content,
|
content: m.content,
|
||||||
@@ -391,8 +393,18 @@ export class ChatView extends ItemView {
|
|||||||
|
|
||||||
this.setStatus("Claude denkt…");
|
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;
|
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) {
|
for (const filePath of this.activeExtensions) {
|
||||||
const file =
|
const file =
|
||||||
this.app.metadataCache.getFirstLinkpathDest(filePath, "") ??
|
this.app.metadataCache.getFirstLinkpathDest(filePath, "") ??
|
||||||
|
|||||||
+23
-1
@@ -22,6 +22,7 @@ export interface MemexChatSettings {
|
|||||||
sendOnEnter: boolean;
|
sendOnEnter: boolean;
|
||||||
contextProperties: string[];
|
contextProperties: string[];
|
||||||
promptButtons: PromptButton[];
|
promptButtons: PromptButton[];
|
||||||
|
systemContextFile: string; // optional vault path for extended system context
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: MemexChatSettings = {
|
export const DEFAULT_SETTINGS: MemexChatSettings = {
|
||||||
@@ -43,6 +44,7 @@ Wenn du Fragen beantwortest:
|
|||||||
threadsFolder: "Calendar/Chat",
|
threadsFolder: "Calendar/Chat",
|
||||||
sendOnEnter: false,
|
sendOnEnter: false,
|
||||||
contextProperties: ["collection", "related", "up", "tags"],
|
contextProperties: ["collection", "related", "up", "tags"],
|
||||||
|
systemContextFile: "",
|
||||||
promptButtons: [
|
promptButtons: [
|
||||||
{
|
{
|
||||||
label: "Draft Check",
|
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 helpLabel = card.createEl("label", { cls: "vc-pbtn-folder-label", text: "Hilfetext (optional, erscheint im Chat wenn Button aktiv):" });
|
||||||
const helpTextArea = card.createEl("textarea", {
|
const helpTextArea = card.createEl("textarea", {
|
||||||
cls: "vc-pbtn-help-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;
|
}) as HTMLTextAreaElement;
|
||||||
helpTextArea.value = pb.helpText ?? "";
|
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 () => {
|
helpTextArea.addEventListener("change", async () => {
|
||||||
pb.helpText = helpTextArea.value.trim() || undefined;
|
pb.helpText = helpTextArea.value.trim() || undefined;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -385,6 +394,19 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
|||||||
textarea.inputEl.style.fontSize = "12px";
|
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 ---
|
// --- Actions ---
|
||||||
containerEl.createEl("h3", { text: "Aktionen" });
|
containerEl.createEl("h3", { text: "Aktionen" });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user