Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

Alle nennenswerten Änderungen an diesem Projekt werden hier dokumentiert.

## [0.10.22-beta] - 2026-07-21

Der Notiz-Agent kann jetzt optional im Web recherchieren, geprüfte Seiten lesen und die verwendeten Quellen nachvollziehbar in die fertige Notiz übernehmen.

### Neu

- **Optionale Webrecherche für den Notiz-Agenten.** Pro Auftrag lässt sich eine Websuche zuschalten. Tavily ist der empfohlene Standard; alternativ funktionieren eine eigene SearXNG-Instanz oder Linkup. Der Agent darf nur Treffer des laufenden Suchlaufs abrufen, schützt lokale und interne Netze vor Zugriffen und ergänzt erfolgreiche Quellen deterministisch in der fertigen Notiz. Anbieter-Konfiguration und API-Keys bleiben im Main-Prozess; die App zeigt vor Cloud-Läufen, welche Daten an welchen Dienst gehen.
- **Ruhigerer Editor mit Kontextspalte.** Die Kopfzeile konzentriert sich auf die wichtigsten Aktionen, während Zusammenhänge und Arbeitskontext in einer eigenen Seitenleiste erreichbar sind.
- **Neue Zentrale „KI & Modelle" und durchsuchbare Einstellungen.** Provider, Modelle und KI-Funktionen sind gebündelt; die Suche springt direkt zum passenden Einstellungsbereich.
- **Ausgebautes Brain.** Zustandssteuerung und Zeitstrahl machen Veränderungen des persönlichen Wissenskontexts nachvollziehbarer.

### Behoben

- **Große Wissensgraphen frieren den Renderer nicht mehr ein.** Der Canvas schützt sich vor einem ungefilterten Riesen-Graphen.
- **Editor-Modusumschalter zeigt wieder Icon und Beschriftung.**

## [0.10.21-beta] - 2026-07-13

Der Zettel-Zielordner lässt sich jetzt fest einstellen — die Schnellerfassung muss den Zettelkasten nicht mehr erraten.
Expand Down
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ Module als verbindbare Bausteine mit **typisierten Ports** auf einem React-Flow-
- `chatClient.chatWithTools()` mapped Ollama-Wire-Format (`role: tool`, `tool_calls.function`) auf interne `ToolCall`-Struktur. Tool-fähige Ollama-Modelle bevorzugt: `qwen3`, `qwen2.5-coder`, `llama3.1`, `mistral-nemo`. Gemma kann **kein** Tool-Calling.
- `safeReplyMarkdown` retried Plain-Text bei Markdown-Parse-Fehlern (Telegram lehnt unbalancierte `*`/`_`/`` ` `` ab).

### Webrecherche des Notiz-Agenten (Opt-in)
- Zweistufiges Opt-in: Modul `web-research` (default aus) + Globus pro Agent-Lauf. Ohne Globus enthält die Tool-Allowlist kein `web_search`/`web_fetch`.
- Provider: **Tavily** (empfohlener Default), **SearXNG** (eigene Instanz) und **Linkup**. Config und verschlüsselte API-Keys liegen ausschließlich Main-seitig in `main/webResearch/config.ts`; Run-Parameter enthalten nur `{ enabled: true }`.
- Egress-Sicherheitskern in `main/webResearch/egress.ts`: DNS-gepinnter Socket, nur global routbare Ziele, same-host-Redirects, Größen-/Dekompressions-/Zeitlimits. Private SearXNG-Origins brauchen eine sichtbare Main-Freigabe; `web_fetch` hat nie eine private Ausnahme.
- Zustandsmaschine in `noteAgent/skills.ts`: `search → fetch → write`, Main-geführte URL-Allowlist, Budgets und genau ein `write_note`. Der Quellenblock wird deterministisch aus erfolgreichen Fetch-Records erzeugt; Webinhalte gelten als untrusted Daten.
- Vollständiger Entwurf/Sicherheitsvertrag: `docs/web-research-plan.md`. Tests: `shared/webResearch.test.ts`, `main/webResearch/security.test.ts`, `noteAgent/webTools.test.ts`, `noteAgent/webFetchRecord.test.ts`, `noteAgent/loopWeb.test.ts`.

### Automatische Backups
- `<vault>/.mindgraph/backups/JJJJ-MM-TT/<relpath>/<dateiname>.<timestamp>.bak` vor jedem `.md`-Write.
- **Hard-Block für leere Writes** auf nicht-leere Markdown-Dateien im `write-file`-Handler — zweite Verteidigungslinie unabhängig vom Editor.
Expand Down
174 changes: 174 additions & 0 deletions MindGraph Website (standalone).html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Lokaler Workspace, der deine Notizen, Aufgaben, E-Mails und Dokumente verbindet
- **Lokal-first**: lokale LLMs via Ollama (Zusammenfassen, Uebersetzen, Weiterschreiben) -- Standard, ohne Cloud-Zwang
- **Cloud opt-in via OpenRouter** (seit v0.8.1): für schwache Hardware optional zuschaltbar, bewusst pro Funktion -- Brain bleibt immer lokal
- **Macher-Leiste (⌘⇧A)**: KI schlägt Änderungen als Block-Diff vor -- du übernimmst oder verwirfst, nichts wird automatisch ersetzt
- **Webrecherche (Opt-in)**: Der Notiz-Agent sucht über Tavily, SearXNG oder Linkup, liest freigegebene Treffer lokal und erzeugt eine gestagede Notiz mit deterministischem Quellenblock
- KI-Kontextmenue (Alt+Rechtsklick) für Textauswahl, Provenienz (Modell + Datum) im Frontmatter
- **Smart Connections**: Ähnlichkeitssuche über Embeddings (bge-m3) mit optionalem LLM-Reranker
- **Eingebautes Diktat** (Whisper STT, lokal im Browser-Runtime) -- Schnellerfassung per ⌘D
Expand Down
4 changes: 2 additions & 2 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mindgraph-notes",
"version": "0.10.21-beta",
"version": "0.10.22-beta",
"description": "Lokaler Workspace, der dir täglich zeigt, was in deinen Notizen, Aufgaben und E-Mails wichtig ist — mit Wissensgraph und KI.",
"main": "out/main/index.js",
"workspaces": [
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4163,7 +4163,7 @@ ipcMain.handle('note-agent-run', async (event, params: NoteAgentRunParams) => {
if (params.webResearch?.enabled) {
const webConfig = await loadWebResearchConfig()
if (!isWebResearchConfigComplete(webConfig)) {
return { success: false, error: 'Webrecherche ist eingeschaltet, aber nicht konfiguriert (Einstellungen → Webrecherche: SearXNG-URL bzw. Linkup-Key).' }
return { success: false, error: 'Webrecherche ist eingeschaltet, aber nicht konfiguriert (Einstellungen → KI & Modelle → Webrecherche: SearXNG-URL oder API-Key für Tavily/Linkup).' }
}
const apiKey = KEY_PROVIDERS.includes(webConfig.provider) ? await loadProviderKey(webConfig.provider) : null
if (KEY_PROVIDERS.includes(webConfig.provider) && !apiKey) {
Expand Down Expand Up @@ -9193,7 +9193,7 @@ function registerCloudProviderIpc(provider: CloudChatBackend): void {
registerCloudProviderIpc('openrouter')
registerCloudProviderIpc('llmbase')

// ── Webrecherche (Opt-in): Provider-Config + Linkup-Key Main-seitig (0d) ──────
// ── Webrecherche (Opt-in): Provider-Config + API-Keys Main-seitig (0d) ────────
// ALLE Handler prüfen isTrustedSender (nur eigener Top-Frame) — fremder, in Markdown
// eingebetteter Inhalt/Sub-Frame kann Config/Key weder lesen/ändern noch Netzproben auslösen.
const WR_UNAUTHORIZED = { success: false, error: 'Nicht autorisierter Aufrufer' } as const
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
projectStatusLoadSynonyms: (vaultPath: string, projectFolderRel: string) =>
ipcRenderer.invoke('project-status-load-synonyms', vaultPath, projectFolderRel),

// Webrecherche (Opt-in): Provider-Config + Linkup-Key liegen Main-seitig (0d),
// Webrecherche (Opt-in): Provider-Config + API-Keys liegen Main-seitig (0d),
// der Renderer verwaltet sie nur über diese Kanäle.
webResearchLoadConfig: () => ipcRenderer.invoke('webresearch-load-config'),
webResearchSaveConfig: (input: { provider?: 'tavily' | 'searxng' | 'linkup'; searxngUrl?: string }) =>
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/webResearch/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ async function searchViaTavily(query: string, apiKey: string | null | undefined,
const key = (apiKey || '').trim()
if (!key) throw new Error('Kein Tavily-API-Key hinterlegt. Bitte in den Einstellungen eintragen (kostenlos bei app.tavily.com).')

// search_depth 'basic' = reine Treffer (Titel + URL + Snippet), keine LLM-Antwort/kein
// serverseitiges Raw-Content-Scraping — die Seiten-Extraktion machen wir lokal (fetchExtract).
// search_depth 'basic' + include_answer/raw_content=false = keine synthetisierte Antwort und
// kein vollständiger bereinigter Seiteninhalt in der API-Antwort. Tavily liefert weiterhin
// eigene NLP-Treffer-Snippets aus seinem Suchindex; den vollständigen Seitenabruf und unsere
// Markdown-Extraktion macht ausschließlich fetchExtract lokal.
let res
try {
res = await safeFetch('https://api.tavily.com/search', {
Expand Down
11 changes: 9 additions & 2 deletions app/src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ const App: React.FC = () => {
const [templateSettingsOpen, setTemplateSettingsOpen] = useState(false)
const [settingsOpen, setSettingsOpen] = useState(false)
const [settingsInitialTab, setSettingsInitialTab] = useState<string | undefined>(undefined)
const [settingsInitialAnchor, setSettingsInitialAnchor] = useState<string | undefined>(undefined)
const [overduePanelOpen, setOverduePanelOpen] = useState(false)
const [tagsPanelOpen, setTagsPanelOpen] = useState(false)
const [smartConnectionsOpen, setSmartConnectionsOpen] = useState(false)
Expand Down Expand Up @@ -294,8 +295,9 @@ const App: React.FC = () => {
// Deep-Links aus HelpGuide/Briefing: Settings-Tab öffnen, Briefing anzeigen
useEffect(() => {
const handleOpenSettings = (e: Event) => {
const detail = (e as CustomEvent<{ tab?: string }>).detail
const detail = (e as CustomEvent<{ tab?: string; anchor?: string }>).detail
if (detail?.tab) setSettingsInitialTab(detail.tab)
setSettingsInitialAnchor(detail?.anchor)
setSettingsOpen(true)
}
const handleShowBriefing = () => setBriefingOpen(true)
Expand Down Expand Up @@ -1659,8 +1661,13 @@ const App: React.FC = () => {
{/* Settings Modal (Cmd+,) */}
<Settings
isOpen={settingsOpen}
onClose={() => { setSettingsOpen(false); setSettingsInitialTab(undefined) }}
onClose={() => {
setSettingsOpen(false)
setSettingsInitialTab(undefined)
setSettingsInitialAnchor(undefined)
}}
initialTab={settingsInitialTab as never}
initialAnchor={settingsInitialAnchor}
/>

{/* What's New Modal (shown after update) */}
Expand Down
10 changes: 5 additions & 5 deletions app/src/renderer/components/BrainIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react'

// Rosa SVG-Gehirn zur Kennzeichnung von Brain-Tagesnotizen.
// Bewusst KEIN 🧠-Emoji (uneinheitliche Plattform-Darstellung) — ein eigenes,
// klar erkennbares Icon in Brain-Rosa.
const BRAIN_ROSA = '#e85aad'
// SVG-Gehirn zur Kennzeichnung von Brain-Tagesnotizen.
// Bewusst KEIN 🧠-Emoji (uneinheitliche Plattform-Darstellung).
// Farbe kommt aus dem Token --brain-color (= Akzentfarbe; Design-Befund B3 —
// vorher hartes Pink #e85aad neben Petrol).

interface BrainIconProps {
size?: number
Expand All @@ -18,7 +18,7 @@ export const BrainIcon: React.FC<BrainIconProps> = ({ size = 14, className, titl
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={BRAIN_ROSA}
stroke="var(--brain-color)"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
Expand Down
Loading
Loading