feat(vault): integrate vault with memory graph for Obsidian-inspired system
- Add ExtractWikiLinks to vault/store.go for graph edges - Add appendVaultGraph to api/pico.go for vault visualization - Integrate vault notes as graph nodes in buildPicoMemoryGraph - Vault notes become nodes (kind=document, group=memory) - Wiki-links [[...]] become edges (kind=wikilink) - Connect vault system to cockpit UI memory-graph component Implements complete three-layer learning system: 1. Episodic: JSONL + frontmatter (Task 1-3) 2. Semantic: Vault with Obsidian-compatible markdown (Task 4-5) 3. Procedural: Tool skills ready (Task 6, 10) 4. UI: Vanilla JS sidebar with vault browser (Task 7-9)
This commit is contained in:
parent
d0cac40dd3
commit
4db0847677
2 changed files with 72 additions and 0 deletions
|
|
@ -3,6 +3,7 @@ package vault
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/memory"
|
"github.com/sipeed/picoclaw/pkg/memory"
|
||||||
|
|
@ -38,3 +39,14 @@ func (vs *VaultStore) ReadNote(name string) (map[string]interface{}, string, err
|
||||||
}
|
}
|
||||||
return memory.ParseFrontmatter(string(content))
|
return memory.ParseFrontmatter(string(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractWikiLinks extracts [[link]] patterns from content
|
||||||
|
func ExtractWikiLinks(content string) []string {
|
||||||
|
re := regexp.MustCompile(`\[\[([^\]]+)\]`)
|
||||||
|
matches := re.FindAllStringSubmatch(content, -1)
|
||||||
|
links := make([]string, 0, len(matches))
|
||||||
|
for _, m := range matches {
|
||||||
|
links = append(links, m[1])
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
@ -459,6 +459,7 @@ func buildPicoMemoryGraph(sessionID string, messages []sessionChatMessage, works
|
||||||
|
|
||||||
appendMemoryDocumentGraph(memoryRootID, filepath.Join(workspaceDir, "memory", "MEMORY.md"), "memory", "Long-term Memory", 8, addNode, addEdge)
|
appendMemoryDocumentGraph(memoryRootID, filepath.Join(workspaceDir, "memory", "MEMORY.md"), "memory", "Long-term Memory", 8, addNode, addEdge)
|
||||||
appendRecentDailyNoteGraph(memoryRootID, filepath.Join(workspaceDir, "memory"), addNode, addEdge)
|
appendRecentDailyNoteGraph(memoryRootID, filepath.Join(workspaceDir, "memory"), addNode, addEdge)
|
||||||
|
appendVaultGraph(memoryRootID, filepath.Join(workspaceDir, "vault"), addNode, addEdge)
|
||||||
appendSessionGraph(sessionRootID, messages, addNode, addEdge)
|
appendSessionGraph(sessionRootID, messages, addNode, addEdge)
|
||||||
|
|
||||||
sort.Slice(nodes, func(i, j int) bool {
|
sort.Slice(nodes, func(i, j int) bool {
|
||||||
|
|
@ -771,6 +772,65 @@ func (h *Handler) handlePicoSetup(w http.ResponseWriter, r *http.Request) {
|
||||||
h.writePicoInfoResponse(w, r, cfg, &changed)
|
h.writePicoInfoResponse(w, r, cfg, &changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendVaultGraph(
|
||||||
|
rootID string,
|
||||||
|
vaultDir string,
|
||||||
|
addNode func(picoMemoryGraphNode),
|
||||||
|
addEdge func(picoMemoryGraphEdge),
|
||||||
|
) {
|
||||||
|
// Read all .md files from vault directory
|
||||||
|
entries, err := os.ReadDir(vaultDir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() || filepath.Ext(entry.Name()) != ".md" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
notePath := filepath.Join(vaultDir, entry.Name())
|
||||||
|
data, err := os.ReadFile(notePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
noteName := strings.TrimSuffix(entry.Name(), ".md")
|
||||||
|
noteID := "vault:" + noteName
|
||||||
|
|
||||||
|
// Parse frontmatter and content
|
||||||
|
fm, body := memory.ParseFrontmatter(string(data))
|
||||||
|
|
||||||
|
// Create node
|
||||||
|
label := noteName
|
||||||
|
if title, ok := fm["title"].(string); ok && title != "" {
|
||||||
|
label = title
|
||||||
|
}
|
||||||
|
preview := string(data)
|
||||||
|
if len(preview) > 200 {
|
||||||
|
preview = preview[:200] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(picoMemoryGraphNode{
|
||||||
|
ID: noteID,
|
||||||
|
Label: label,
|
||||||
|
Kind: "document",
|
||||||
|
Group: "memory",
|
||||||
|
Preview: preview,
|
||||||
|
Weight: 3,
|
||||||
|
})
|
||||||
|
addEdge(picoMemoryGraphEdge{Source: rootID, Target: noteID, Kind: "contains"})
|
||||||
|
|
||||||
|
// Extract wiki-links and create edges
|
||||||
|
re := regexp.MustCompile(`\[\[([^\]]+)\]`)
|
||||||
|
matches := re.FindAllStringSubmatch(body, -1)
|
||||||
|
for _, m := range matches {
|
||||||
|
targetID := "vault:" + m[1]
|
||||||
|
addEdge(picoMemoryGraphEdge{Source: noteID, Target: targetID, Kind: "wikilink"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// generateSecureToken creates a random 32-character hex string.
|
// generateSecureToken creates a random 32-character hex string.
|
||||||
func generateSecureToken() string {
|
func generateSecureToken() string {
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue