diff --git a/main.js b/main.js
index 756da3c..839de40 100644
--- a/main.js
+++ b/main.js
@@ -337,6 +337,7 @@ ${content}`;
const stream = this.plugin.claude.streamChat(claudeMessages, {
apiKey: this.plugin.settings.apiKey,
model: this.plugin.settings.model,
+ maxTokens: this.plugin.settings.maxTokens,
systemPrompt
});
for await (const chunk of stream) {
@@ -556,6 +557,45 @@ ${content}`;
link.onclick = () => this.app.workspace.openLinkText(notePath, "", "tab");
}
}
+ if (!msg.isStreaming && msg.role === "assistant") {
+ const actions = msgEl.createDiv("vc-msg-actions");
+ const copyBtn = actions.createEl("button", { cls: "vc-msg-action-btn", title: "Antwort kopieren" });
+ copyBtn.innerHTML = ` Kopieren`;
+ copyBtn.onclick = async () => {
+ await navigator.clipboard.writeText(msg.content);
+ copyBtn.textContent = "\u2713 Kopiert";
+ setTimeout(() => {
+ copyBtn.innerHTML = ` Kopieren`;
+ }, 2e3);
+ };
+ const saveBtn = actions.createEl("button", { cls: "vc-msg-action-btn", title: "Als neue Notiz speichern" });
+ saveBtn.innerHTML = ` Als Notiz`;
+ saveBtn.onclick = async () => {
+ await this.saveResponseAsNote(msg.content);
+ };
+ }
+ }
+ async saveResponseAsNote(content) {
+ var _a;
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
+ const firstLine = (_a = content.split("\n").find((l) => l.trim())) != null ? _a : "Claude Antwort";
+ const title = firstLine.replace(/^#+\s*/, "").replace(/[\\/:*?"<>|]/g, " ").slice(0, 60).trim();
+ const noteContent = `---
+created: ${date}
+tags: [chat, claude]
+---
+
+${content}`;
+ try {
+ const folder = this.app.fileManager.getNewFileParent("");
+ const folderPath = folder.path === "/" ? "" : folder.path + "/";
+ const fileName = `${folderPath}${date} ${title}.md`;
+ const existing = this.app.vault.getAbstractFileByPath(fileName);
+ const file = existing instanceof import_obsidian.TFile ? await this.app.vault.modify(existing, noteContent).then(() => existing) : await this.app.vault.create(fileName, noteContent);
+ this.app.workspace.openLinkText(file.path, "", "tab");
+ } catch (e) {
+ this.setStatus("\u26A0 Fehler beim Speichern: " + e.message);
+ }
}
updateLastMessage(content) {
const messages = this.messagesEl.querySelectorAll(".vc-msg--assistant");
@@ -1140,7 +1180,7 @@ var ClaudeClient = class {
headers: this.headers(options.apiKey),
body: JSON.stringify({
model: options.model,
- max_tokens: (_a = options.maxTokens) != null ? _a : 2048,
+ max_tokens: (_a = options.maxTokens) != null ? _a : 8192,
system: options.systemPrompt,
messages
}),
@@ -1163,7 +1203,7 @@ var ClaudeClient = class {
headers: this.headers(options.apiKey),
body: JSON.stringify({
model: options.model,
- max_tokens: (_a = options.maxTokens) != null ? _a : 2048,
+ max_tokens: (_a = options.maxTokens) != null ? _a : 8192,
system: options.systemPrompt,
messages
}),
@@ -1181,6 +1221,7 @@ var import_obsidian3 = require("obsidian");
var DEFAULT_SETTINGS = {
apiKey: "",
model: "claude-opus-4-5-20251101",
+ maxTokens: 8192,
maxContextNotes: 6,
maxCharsPerNote: 2500,
systemPrompt: `Du bist ein hilfreicher Assistent mit Zugriff auf die pers\xF6nliche Wissensdatenbank des Nutzers (Obsidian Vault).
@@ -1241,6 +1282,12 @@ var MemexChatSettingsTab = class extends import_obsidian3.PluginSettingTab {
await this.plugin.saveSettings();
});
});
+ new import_obsidian3.Setting(containerEl).setName("Max. Antwort-Tokens").setDesc("Maximale L\xE4nge der Claude-Antwort. F\xFCr lange Analysen (z.B. Monthly Check) h\xF6her einstellen. (1024\u201316000)").addSlider(
+ (slider) => slider.setLimits(1024, 16e3, 512).setValue(this.plugin.settings.maxTokens).setDynamicTooltip().onChange(async (value) => {
+ this.plugin.settings.maxTokens = value;
+ await this.plugin.saveSettings();
+ })
+ );
new import_obsidian3.Setting(containerEl).setName("Senden mit Enter").setDesc("Ein: Enter sendet. Aus: Cmd+Enter sendet (Enter = neue Zeile)").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.sendOnEnter).onChange(async (value) => {
this.plugin.settings.sendOnEnter = value;
diff --git a/manifest.json b/manifest.json
index c9f33af..4066a72 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"id": "memex-chat",
"name": "Memex Chat",
- "version": "0.2.1",
+ "version": "0.2.2",
"minAppVersion": "1.4.0",
"description": "Chat with your Obsidian vault using Claude AI — semantic context retrieval, @ mentions, thread history.",
"author": "Sven",
diff --git a/package.json b/package.json
index 245093b..88ceb22 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "memex-chat",
- "version": "0.2.1",
+ "version": "0.2.2",
"description": "Obsidian plugin: Chat with your vault using Claude AI",
"main": "main.js",
"scripts": {
diff --git a/src/ChatView.ts b/src/ChatView.ts
index b955dee..4df19f4 100644
--- a/src/ChatView.ts
+++ b/src/ChatView.ts
@@ -420,6 +420,7 @@ export class ChatView extends ItemView {
const stream = this.plugin.claude.streamChat(claudeMessages, {
apiKey: this.plugin.settings.apiKey,
model: this.plugin.settings.model,
+ maxTokens: this.plugin.settings.maxTokens,
systemPrompt,
});
@@ -661,6 +662,50 @@ export class ChatView extends ItemView {
link.onclick = () => this.app.workspace.openLinkText(notePath, "", "tab");
}
}
+
+ // Action buttons for finished assistant messages
+ if (!msg.isStreaming && msg.role === "assistant") {
+ const actions = msgEl.createDiv("vc-msg-actions");
+
+ // Copy button
+ const copyBtn = actions.createEl("button", { cls: "vc-msg-action-btn", title: "Antwort kopieren" });
+ copyBtn.innerHTML = ` Kopieren`;
+ copyBtn.onclick = async () => {
+ await navigator.clipboard.writeText(msg.content);
+ copyBtn.textContent = "✓ Kopiert";
+ setTimeout(() => {
+ copyBtn.innerHTML = ` Kopieren`;
+ }, 2000);
+ };
+
+ // Save as note button
+ const saveBtn = actions.createEl("button", { cls: "vc-msg-action-btn", title: "Als neue Notiz speichern" });
+ saveBtn.innerHTML = ` Als Notiz`;
+ saveBtn.onclick = async () => {
+ await this.saveResponseAsNote(msg.content);
+ };
+ }
+ }
+
+ private async saveResponseAsNote(content: string): Promise {
+ const date = new Date().toISOString().slice(0, 10);
+ // Use first non-empty line as title (max 60 chars, strip markdown headers)
+ const firstLine = content.split("\n").find((l) => l.trim()) ?? "Claude Antwort";
+ const title = firstLine.replace(/^#+\s*/, "").replace(/[\\/:*?"<>|]/g, " ").slice(0, 60).trim();
+ const noteContent = `---\ncreated: ${date}\ntags: [chat, claude]\n---\n\n${content}`;
+ try {
+ // Use Obsidian's configured default new-note folder
+ const folder = this.app.fileManager.getNewFileParent("");
+ const folderPath = folder.path === "/" ? "" : folder.path + "/";
+ const fileName = `${folderPath}${date} ${title}.md`;
+ const existing = this.app.vault.getAbstractFileByPath(fileName);
+ const file = existing instanceof TFile
+ ? await this.app.vault.modify(existing, noteContent).then(() => existing)
+ : await this.app.vault.create(fileName, noteContent);
+ this.app.workspace.openLinkText(file.path, "", "tab");
+ } catch (e) {
+ this.setStatus("⚠ Fehler beim Speichern: " + e.message);
+ }
}
private updateLastMessage(content: string): void {
diff --git a/src/ClaudeClient.ts b/src/ClaudeClient.ts
index 0c857e9..b7c0587 100644
--- a/src/ClaudeClient.ts
+++ b/src/ClaudeClient.ts
@@ -45,7 +45,7 @@ export class ClaudeClient {
headers: this.headers(options.apiKey),
body: JSON.stringify({
model: options.model,
- max_tokens: options.maxTokens ?? 2048,
+ max_tokens: options.maxTokens ?? 8192,
system: options.systemPrompt,
messages,
}),
@@ -70,7 +70,7 @@ export class ClaudeClient {
headers: this.headers(options.apiKey),
body: JSON.stringify({
model: options.model,
- max_tokens: options.maxTokens ?? 2048,
+ max_tokens: options.maxTokens ?? 8192,
system: options.systemPrompt,
messages,
}),
diff --git a/src/SettingsTab.ts b/src/SettingsTab.ts
index be5f27a..c09886b 100644
--- a/src/SettingsTab.ts
+++ b/src/SettingsTab.ts
@@ -12,6 +12,7 @@ export interface PromptButton {
export interface MemexChatSettings {
apiKey: string;
model: string;
+ maxTokens: number;
maxContextNotes: number;
maxCharsPerNote: number;
systemPrompt: string;
@@ -28,6 +29,7 @@ export interface MemexChatSettings {
export const DEFAULT_SETTINGS: MemexChatSettings = {
apiKey: "",
model: "claude-opus-4-5-20251101",
+ maxTokens: 8192,
maxContextNotes: 6,
maxCharsPerNote: 2500,
systemPrompt: `Du bist ein hilfreicher Assistent mit Zugriff auf die persönliche Wissensdatenbank des Nutzers (Obsidian Vault).
@@ -107,6 +109,20 @@ export class MemexChatSettingsTab extends PluginSettingTab {
});
});
+ new Setting(containerEl)
+ .setName("Max. Antwort-Tokens")
+ .setDesc("Maximale Länge der Claude-Antwort. Für lange Analysen (z.B. Monthly Check) höher einstellen. (1024–16000)")
+ .addSlider((slider) =>
+ slider
+ .setLimits(1024, 16000, 512)
+ .setValue(this.plugin.settings.maxTokens)
+ .setDynamicTooltip()
+ .onChange(async (value) => {
+ this.plugin.settings.maxTokens = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
new Setting(containerEl)
.setName("Senden mit Enter")
.setDesc("Ein: Enter sendet. Aus: Cmd+Enter sendet (Enter = neue Zeile)")
diff --git a/styles.css b/styles.css
index 2da7537..46ae342 100644
--- a/styles.css
+++ b/styles.css
@@ -498,6 +498,38 @@
background: var(--background-modifier-hover);
}
+/* Message action buttons (Copy, Save as Note) */
+.vc-msg-actions {
+ display: flex;
+ gap: 6px;
+ margin-top: 6px;
+ opacity: 0;
+ transition: opacity 0.15s;
+}
+
+.vc-msg--assistant:hover .vc-msg-actions {
+ opacity: 1;
+}
+
+.vc-msg-action-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ background: none;
+ border: 1px solid var(--background-modifier-border);
+ border-radius: 4px;
+ padding: 2px 8px;
+ font-size: 11px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: background 0.1s, color 0.1s;
+}
+
+.vc-msg-action-btn:hover {
+ background: var(--background-modifier-hover);
+ color: var(--text-normal);
+}
+
/* Context Preview */
.vc-context-preview {
padding: 8px 12px;