@powerhousedao/knowledge-note 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +359 -122
- package/dist/editors/knowledge-vault/components/DriveExplorer.d.ts.map +1 -1
- package/dist/editors/knowledge-vault/components/DriveExplorer.js +8 -2
- package/dist/editors/knowledge-vault/components/GraphView.d.ts.map +1 -1
- package/dist/editors/knowledge-vault/components/GraphView.js +210 -32
- package/dist/editors/knowledge-vault/components/NoteList.d.ts.map +1 -1
- package/dist/editors/knowledge-vault/components/NoteList.js +13 -16
- package/dist/editors/knowledge-vault/components/SearchView.d.ts +2 -0
- package/dist/editors/knowledge-vault/components/SearchView.d.ts.map +1 -0
- package/dist/editors/knowledge-vault/components/SearchView.js +96 -0
- package/dist/editors/knowledge-vault/hooks/use-drive-init.d.ts.map +1 -1
- package/dist/editors/knowledge-vault/hooks/use-drive-init.js +0 -2
- package/dist/editors/knowledge-vault/hooks/use-graph-search.d.ts +25 -0
- package/dist/editors/knowledge-vault/hooks/use-graph-search.d.ts.map +1 -0
- package/dist/editors/knowledge-vault/hooks/use-graph-search.js +198 -0
- package/dist/package.json +3 -2
- package/dist/processors/factory.d.ts.map +1 -1
- package/dist/processors/factory.js +0 -3
- package/dist/processors/graph-indexer/embedder.d.ts +5 -0
- package/dist/processors/graph-indexer/embedder.d.ts.map +1 -0
- package/dist/processors/graph-indexer/embedder.js +27 -0
- package/dist/processors/graph-indexer/embedding-store.d.ts +11 -0
- package/dist/processors/graph-indexer/embedding-store.d.ts.map +1 -0
- package/dist/processors/graph-indexer/embedding-store.js +70 -0
- package/dist/processors/graph-indexer/index.d.ts.map +1 -1
- package/dist/processors/graph-indexer/index.js +53 -8
- package/dist/processors/graph-indexer/migrations.d.ts.map +1 -1
- package/dist/processors/graph-indexer/migrations.js +35 -0
- package/dist/processors/graph-indexer/query.d.ts +21 -0
- package/dist/processors/graph-indexer/query.d.ts.map +1 -1
- package/dist/processors/graph-indexer/query.js +154 -13
- package/dist/processors/graph-indexer/schema.d.ts +11 -0
- package/dist/processors/graph-indexer/schema.d.ts.map +1 -1
- package/dist/style.css +130 -0
- package/dist/subgraphs/index.d.ts +0 -1
- package/dist/subgraphs/index.d.ts.map +1 -1
- package/dist/subgraphs/index.js +0 -1
- package/dist/subgraphs/knowledge-graph/subgraph.d.ts +348 -24
- package/dist/subgraphs/knowledge-graph/subgraph.d.ts.map +1 -1
- package/dist/subgraphs/knowledge-graph/subgraph.js +334 -20
- package/package.json +4 -3
- package/dist/processors/methodology-indexer/factory.d.ts +0 -4
- package/dist/processors/methodology-indexer/factory.d.ts.map +0 -1
- package/dist/processors/methodology-indexer/factory.js +0 -23
- package/dist/processors/methodology-indexer/index.d.ts +0 -11
- package/dist/processors/methodology-indexer/index.d.ts.map +0 -1
- package/dist/processors/methodology-indexer/index.js +0 -116
- package/dist/processors/methodology-indexer/migrations.d.ts +0 -4
- package/dist/processors/methodology-indexer/migrations.d.ts.map +0 -1
- package/dist/processors/methodology-indexer/migrations.js +0 -39
- package/dist/processors/methodology-indexer/query.d.ts +0 -35
- package/dist/processors/methodology-indexer/query.d.ts.map +0 -1
- package/dist/processors/methodology-indexer/query.js +0 -114
- package/dist/processors/methodology-indexer/schema.d.ts +0 -22
- package/dist/processors/methodology-indexer/schema.d.ts.map +0 -1
- package/dist/processors/methodology-indexer/schema.js +0 -1
- package/dist/subgraphs/methodology/index.d.ts +0 -2
- package/dist/subgraphs/methodology/index.d.ts.map +0 -1
- package/dist/subgraphs/methodology/index.js +0 -1
- package/dist/subgraphs/methodology/subgraph.d.ts +0 -47
- package/dist/subgraphs/methodology/subgraph.d.ts.map +0 -1
- package/dist/subgraphs/methodology/subgraph.js +0 -100
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
2
|
+
import { useSelectedDriveId } from "@powerhousedao/reactor-browser";
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
/* GraphQL helpers */
|
|
5
|
+
/* ------------------------------------------------------------------ */
|
|
6
|
+
const DEBOUNCE_MS = 300;
|
|
7
|
+
const SUBGRAPH_PATH = "/graphql/knowledgeGraph";
|
|
8
|
+
/**
|
|
9
|
+
* Derive the subgraph endpoint.
|
|
10
|
+
*
|
|
11
|
+
* Priority:
|
|
12
|
+
* 1. VITE_SUBGRAPH_URL env var (explicit override for deployed environments)
|
|
13
|
+
* 2. Vite dev (port 3000/3001) → localhost:4001
|
|
14
|
+
* 3. Same-origin relative path (Connect production — app and reactor share origin)
|
|
15
|
+
*
|
|
16
|
+
* For deployed environments where Connect and Switchboard are on different
|
|
17
|
+
* domains (e.g. connect.example.com vs switchboard-dev.powerhouse.xyz),
|
|
18
|
+
* set VITE_SUBGRAPH_URL=https://switchboard-dev.powerhouse.xyz/graphql/knowledgeGraph
|
|
19
|
+
* in your .env file.
|
|
20
|
+
*/
|
|
21
|
+
function resolveEndpoint() {
|
|
22
|
+
// Explicit override via env var
|
|
23
|
+
const envUrl = typeof import.meta !== "undefined" &&
|
|
24
|
+
import.meta.env?.VITE_SUBGRAPH_URL;
|
|
25
|
+
if (envUrl)
|
|
26
|
+
return envUrl;
|
|
27
|
+
// Vite dev server proxying to local reactor
|
|
28
|
+
const port = globalThis.window?.location?.port;
|
|
29
|
+
if (port === "3000" || port === "3001") {
|
|
30
|
+
return `http://localhost:4001${SUBGRAPH_PATH}`;
|
|
31
|
+
}
|
|
32
|
+
// Same-origin (Connect production)
|
|
33
|
+
return SUBGRAPH_PATH;
|
|
34
|
+
}
|
|
35
|
+
async function graphqlFetch(endpoint, query, variables) {
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(endpoint, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ query, variables }),
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
return null;
|
|
44
|
+
const json = (await res.json());
|
|
45
|
+
if (json.errors) {
|
|
46
|
+
console.warn("[useGraphSearch] GraphQL errors:", json.errors);
|
|
47
|
+
}
|
|
48
|
+
return json.data ?? null;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.warn("[useGraphSearch] Fetch failed:", err);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/* ------------------------------------------------------------------ */
|
|
56
|
+
/* Queries */
|
|
57
|
+
/* ------------------------------------------------------------------ */
|
|
58
|
+
const SEMANTIC_SEARCH_QUERY = `
|
|
59
|
+
query SemanticSearch($driveId: ID!, $query: String!, $limit: Int) {
|
|
60
|
+
knowledgeGraphSemanticSearch(driveId: $driveId, query: $query, limit: $limit) {
|
|
61
|
+
node { documentId title description noteType status topics }
|
|
62
|
+
similarity
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
const KEYWORD_SEARCH_QUERY = `
|
|
67
|
+
query FullSearch($driveId: ID!, $query: String!, $limit: Int) {
|
|
68
|
+
knowledgeGraphFullSearch(driveId: $driveId, query: $query, limit: $limit) {
|
|
69
|
+
documentId title description noteType status topics
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
const TOPICS_QUERY = `
|
|
74
|
+
query Topics($driveId: ID!) {
|
|
75
|
+
knowledgeGraphTopics(driveId: $driveId) { name noteCount }
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
/* ------------------------------------------------------------------ */
|
|
79
|
+
/* Hook */
|
|
80
|
+
/* ------------------------------------------------------------------ */
|
|
81
|
+
const STORAGE_KEY = "bai-search-state";
|
|
82
|
+
function loadSearchState() {
|
|
83
|
+
try {
|
|
84
|
+
const raw = sessionStorage.getItem(STORAGE_KEY);
|
|
85
|
+
if (raw) {
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
return {
|
|
88
|
+
query: parsed.query ?? "",
|
|
89
|
+
mode: parsed.mode === "keyword" ? "keyword" : "semantic",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// ignore
|
|
95
|
+
}
|
|
96
|
+
return { query: "", mode: "semantic" };
|
|
97
|
+
}
|
|
98
|
+
function saveSearchState(query, mode) {
|
|
99
|
+
try {
|
|
100
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ query, mode }));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// ignore
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export function useGraphSearch() {
|
|
107
|
+
const driveId = useSelectedDriveId();
|
|
108
|
+
const saved = useRef(loadSearchState());
|
|
109
|
+
const [query, setQueryRaw] = useState(saved.current.query);
|
|
110
|
+
const [results, setResults] = useState([]);
|
|
111
|
+
const [topics, setTopics] = useState([]);
|
|
112
|
+
const [loading, setLoading] = useState(false);
|
|
113
|
+
const [error, setError] = useState(null);
|
|
114
|
+
const [searchMode, setSearchModeRaw] = useState(saved.current.mode);
|
|
115
|
+
const debounceRef = useRef(null);
|
|
116
|
+
const endpoint = useMemo(() => resolveEndpoint(), []);
|
|
117
|
+
// Persist query and mode to sessionStorage
|
|
118
|
+
const setQuery = useCallback((q) => {
|
|
119
|
+
setQueryRaw(q);
|
|
120
|
+
saveSearchState(q, searchMode);
|
|
121
|
+
}, [searchMode]);
|
|
122
|
+
const setSearchMode = useCallback((m) => {
|
|
123
|
+
setSearchModeRaw(m);
|
|
124
|
+
saveSearchState(query, m);
|
|
125
|
+
}, [query]);
|
|
126
|
+
// Fetch topics on mount for empty-state overview
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!driveId)
|
|
129
|
+
return;
|
|
130
|
+
graphqlFetch(endpoint, TOPICS_QUERY, { driveId }).then((data) => {
|
|
131
|
+
if (data?.knowledgeGraphTopics) {
|
|
132
|
+
setTopics(data.knowledgeGraphTopics);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}, [driveId, endpoint]);
|
|
136
|
+
// Debounced search
|
|
137
|
+
const executeSearch = useCallback(async (q, mode) => {
|
|
138
|
+
if (!driveId || !q.trim()) {
|
|
139
|
+
setResults([]);
|
|
140
|
+
setLoading(false);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
setLoading(true);
|
|
144
|
+
setError(null);
|
|
145
|
+
if (mode === "semantic") {
|
|
146
|
+
const data = await graphqlFetch(endpoint, SEMANTIC_SEARCH_QUERY, { driveId, query: q, limit: 20 });
|
|
147
|
+
if (data?.knowledgeGraphSemanticSearch) {
|
|
148
|
+
setResults(data.knowledgeGraphSemanticSearch.map((r) => ({
|
|
149
|
+
...r.node,
|
|
150
|
+
similarity: r.similarity,
|
|
151
|
+
})));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
setResults([]);
|
|
155
|
+
setError("Semantic search unavailable. Try keyword mode.");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const data = await graphqlFetch(endpoint, KEYWORD_SEARCH_QUERY, { driveId, query: q, limit: 20 });
|
|
160
|
+
if (data?.knowledgeGraphFullSearch) {
|
|
161
|
+
setResults(data.knowledgeGraphFullSearch);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
setResults([]);
|
|
165
|
+
setError("Search failed.");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
setLoading(false);
|
|
169
|
+
}, [driveId, endpoint]);
|
|
170
|
+
// Trigger debounced search on query or mode change
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (debounceRef.current)
|
|
173
|
+
clearTimeout(debounceRef.current);
|
|
174
|
+
if (!query.trim()) {
|
|
175
|
+
setResults([]);
|
|
176
|
+
setLoading(false);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
setLoading(true);
|
|
180
|
+
debounceRef.current = setTimeout(() => {
|
|
181
|
+
void executeSearch(query, searchMode);
|
|
182
|
+
}, DEBOUNCE_MS);
|
|
183
|
+
return () => {
|
|
184
|
+
if (debounceRef.current)
|
|
185
|
+
clearTimeout(debounceRef.current);
|
|
186
|
+
};
|
|
187
|
+
}, [query, searchMode, executeSearch]);
|
|
188
|
+
return {
|
|
189
|
+
query,
|
|
190
|
+
setQuery,
|
|
191
|
+
results,
|
|
192
|
+
topics,
|
|
193
|
+
loading,
|
|
194
|
+
error,
|
|
195
|
+
searchMode,
|
|
196
|
+
setSearchMode,
|
|
197
|
+
};
|
|
198
|
+
}
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/knowledge-note",
|
|
3
3
|
"description": "Knowledge Note document model package for Powerhouse",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -107,13 +107,14 @@
|
|
|
107
107
|
},
|
|
108
108
|
"dependencies": {
|
|
109
109
|
"@electric-sql/pglite": "0.3.15",
|
|
110
|
+
"@huggingface/transformers": "^4.0.1",
|
|
110
111
|
"@powerhousedao/builder-tools": "6.0.0-dev.105",
|
|
111
112
|
"@powerhousedao/common": "6.0.0-dev.105",
|
|
112
113
|
"@powerhousedao/design-system": "6.0.0-dev.105",
|
|
113
114
|
"@powerhousedao/document-engineering": "1.40.1",
|
|
114
115
|
"@powerhousedao/vetra": "6.0.0-dev.105",
|
|
115
116
|
"cytoscape": "^3.33.1",
|
|
116
|
-
"cytoscape-
|
|
117
|
+
"cytoscape-fcose": "^2.2.0",
|
|
117
118
|
"document-model": "6.0.0-dev.105",
|
|
118
119
|
"graphql": "16.12.0",
|
|
119
120
|
"graphql-tag": "^2.12.6",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../processors/factory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EAErB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../processors/factory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EAErB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGvD,eAAO,MAAM,gBAAgB,GAAI,QAAQ,oBAAoB,MAa7C,aAAa,gBAAgB,KAAG,OAAO,CAAC,eAAe,EAAE,CAUxE,CAAC"}
|
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
* This file aggregates all processor factories
|
|
3
3
|
*/
|
|
4
4
|
import { graphIndexerProcessorFactory } from "./graph-indexer/factory.js";
|
|
5
|
-
import { methodologyIndexerProcessorFactory } from "./methodology-indexer/factory.js";
|
|
6
5
|
export const processorFactory = (module) => {
|
|
7
6
|
console.log(`[processorFactory] Initializing with processorApp: ${module.processorApp}`);
|
|
8
7
|
const factories = [];
|
|
9
8
|
// Register graph indexer (watches bai/knowledge-note)
|
|
10
9
|
factories.push(graphIndexerProcessorFactory(module));
|
|
11
|
-
// Register methodology indexer (watches bai/research-claim)
|
|
12
|
-
factories.push(methodologyIndexerProcessorFactory(module));
|
|
13
10
|
console.log(`[processorFactory] Loaded ${factories.length} factories`);
|
|
14
11
|
// Return the inner function that will be called for each drive
|
|
15
12
|
return async (driveHeader) => {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type FeatureExtractionPipeline } from "@huggingface/transformers";
|
|
2
|
+
export declare function getExtractor(): Promise<FeatureExtractionPipeline>;
|
|
3
|
+
export declare function generateEmbedding(text: string): Promise<number[]>;
|
|
4
|
+
export declare function isEmbedderReady(): boolean;
|
|
5
|
+
//# sourceMappingURL=embedder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/embedder.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,2BAA2B,CAAC;AAKnC,wBAAsB,YAAY,IAAI,OAAO,CAAC,yBAAyB,CAAC,CAgBvE;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAIvE;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { pipeline, } from "@huggingface/transformers";
|
|
2
|
+
let extractor = null;
|
|
3
|
+
let loading = null;
|
|
4
|
+
export async function getExtractor() {
|
|
5
|
+
if (extractor)
|
|
6
|
+
return extractor;
|
|
7
|
+
// Prevent concurrent loads — share the same promise
|
|
8
|
+
if (!loading) {
|
|
9
|
+
loading = pipeline("feature-extraction", "Supabase/gte-small", {
|
|
10
|
+
dtype: "q8",
|
|
11
|
+
}).then((ext) => {
|
|
12
|
+
extractor = ext;
|
|
13
|
+
loading = null;
|
|
14
|
+
console.log("[Embedder] Model loaded: Supabase/gte-small (q8)");
|
|
15
|
+
return extractor;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return loading;
|
|
19
|
+
}
|
|
20
|
+
export async function generateEmbedding(text) {
|
|
21
|
+
const ext = await getExtractor();
|
|
22
|
+
const output = await ext(text, { pooling: "mean", normalize: true });
|
|
23
|
+
return Array.from(output.data);
|
|
24
|
+
}
|
|
25
|
+
export function isEmbedderReady() {
|
|
26
|
+
return extractor !== null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
export declare function getEmbeddingDb(): Promise<PGlite>;
|
|
3
|
+
export declare function upsertEmbedding(documentId: string, embedding: number[]): Promise<void>;
|
|
4
|
+
export declare function searchSimilar(queryEmbedding: number[], limit?: number): Promise<Array<{
|
|
5
|
+
documentId: string;
|
|
6
|
+
similarity: number;
|
|
7
|
+
}>>;
|
|
8
|
+
export declare function getEmbedding(documentId: string): Promise<number[] | null>;
|
|
9
|
+
export declare function deleteEmbedding(documentId: string): Promise<void>;
|
|
10
|
+
export declare function closeEmbeddingDb(): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=embedding-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-store.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/embedding-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAK9C,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CA2BtD;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,aAAa,CACjC,cAAc,EAAE,MAAM,EAAE,EACxB,KAAK,SAAK,GACT,OAAO,CAAC,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAmB5D;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAa1B;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvE;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKtD"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import { vector } from "@electric-sql/pglite/vector";
|
|
3
|
+
let db = null;
|
|
4
|
+
export async function getEmbeddingDb() {
|
|
5
|
+
if (db)
|
|
6
|
+
return db;
|
|
7
|
+
const dataDir = typeof window !== "undefined"
|
|
8
|
+
? "idb://knowledge-embeddings"
|
|
9
|
+
: "./.ph/knowledge-embeddings";
|
|
10
|
+
db = new PGlite({
|
|
11
|
+
dataDir,
|
|
12
|
+
extensions: { vector },
|
|
13
|
+
});
|
|
14
|
+
await db.exec("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
15
|
+
await db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS note_embeddings (
|
|
17
|
+
document_id VARCHAR(255) PRIMARY KEY,
|
|
18
|
+
embedding vector(384) NOT NULL,
|
|
19
|
+
updated_at VARCHAR(50) NOT NULL
|
|
20
|
+
);
|
|
21
|
+
`);
|
|
22
|
+
await db.exec(`
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_note_embeddings_hnsw
|
|
24
|
+
ON note_embeddings USING hnsw (embedding vector_cosine_ops);
|
|
25
|
+
`);
|
|
26
|
+
return db;
|
|
27
|
+
}
|
|
28
|
+
export async function upsertEmbedding(documentId, embedding) {
|
|
29
|
+
const store = await getEmbeddingDb();
|
|
30
|
+
const now = new Date().toISOString();
|
|
31
|
+
const embeddingStr = `[${embedding.join(",")}]`;
|
|
32
|
+
await store.query(`INSERT INTO note_embeddings (document_id, embedding, updated_at)
|
|
33
|
+
VALUES ($1, $2::vector, $3)
|
|
34
|
+
ON CONFLICT (document_id) DO UPDATE SET
|
|
35
|
+
embedding = EXCLUDED.embedding,
|
|
36
|
+
updated_at = EXCLUDED.updated_at`, [documentId, embeddingStr, now]);
|
|
37
|
+
}
|
|
38
|
+
export async function searchSimilar(queryEmbedding, limit = 10) {
|
|
39
|
+
const store = await getEmbeddingDb();
|
|
40
|
+
const embeddingStr = `[${queryEmbedding.join(",")}]`;
|
|
41
|
+
const result = await store.query(`SELECT document_id, embedding <=> $1::vector AS distance
|
|
42
|
+
FROM note_embeddings
|
|
43
|
+
ORDER BY distance ASC
|
|
44
|
+
LIMIT $2`, [embeddingStr, limit]);
|
|
45
|
+
return (result.rows ?? []).map((row) => ({
|
|
46
|
+
documentId: row.document_id,
|
|
47
|
+
similarity: 1 - row.distance,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
export async function getEmbedding(documentId) {
|
|
51
|
+
const store = await getEmbeddingDb();
|
|
52
|
+
const result = await store.query(`SELECT embedding::text FROM note_embeddings WHERE document_id = $1`, [documentId]);
|
|
53
|
+
if (!result.rows || result.rows.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
// Parse "[0.1,0.2,...]" string back to number[]
|
|
56
|
+
const raw = result.rows[0].embedding;
|
|
57
|
+
return JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
export async function deleteEmbedding(documentId) {
|
|
60
|
+
const store = await getEmbeddingDb();
|
|
61
|
+
await store.query(`DELETE FROM note_embeddings WHERE document_id = $1`, [
|
|
62
|
+
documentId,
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
export async function closeEmbeddingDb() {
|
|
66
|
+
if (db) {
|
|
67
|
+
await db.close();
|
|
68
|
+
db = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAItC,qBAAa,qBAAsB,SAAQ,qBAAqB,CAAC,EAAE,CAAC;WAClD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAItC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,YAAY,CACzB,UAAU,EAAE,oBAAoB,EAAE,GACjC,OAAO,CAAC,IAAI,CAAC;IAmKV,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAQrB,UAAU;CA8BzB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { RelationalDbProcessor } from "@powerhousedao/shared/processors";
|
|
2
2
|
import { up } from "./migrations.js";
|
|
3
|
+
import { generateEmbedding } from "./embedder.js";
|
|
4
|
+
import { upsertEmbedding, deleteEmbedding } from "./embedding-store.js";
|
|
3
5
|
export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
4
6
|
static getNamespace(driveId) {
|
|
5
7
|
return super.getNamespace(driveId);
|
|
@@ -41,6 +43,8 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
|
41
43
|
// resultingState may be wrapped in { global: ... } or be the global state directly
|
|
42
44
|
const global = (parsed.global ?? parsed);
|
|
43
45
|
const now = new Date().toISOString();
|
|
46
|
+
// Extract provenance
|
|
47
|
+
const provenance = global.provenance;
|
|
44
48
|
// Upsert node
|
|
45
49
|
await this.relationalDb
|
|
46
50
|
.insertInto("graph_nodes")
|
|
@@ -51,6 +55,10 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
|
51
55
|
description: global.description ?? null,
|
|
52
56
|
note_type: global.noteType ?? null,
|
|
53
57
|
status: global.status ?? "DRAFT",
|
|
58
|
+
content: global.content ?? null,
|
|
59
|
+
author: provenance?.author ?? null,
|
|
60
|
+
source_origin: provenance?.sourceOrigin ?? null,
|
|
61
|
+
created_at: provenance?.createdAt ?? null,
|
|
54
62
|
updated_at: now,
|
|
55
63
|
})
|
|
56
64
|
.onConflict((oc) => oc.column("document_id").doUpdateSet({
|
|
@@ -58,9 +66,35 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
|
58
66
|
description: global.description ?? null,
|
|
59
67
|
note_type: global.noteType ?? null,
|
|
60
68
|
status: global.status ?? "DRAFT",
|
|
69
|
+
content: global.content ?? null,
|
|
70
|
+
author: provenance?.author ?? null,
|
|
71
|
+
source_origin: provenance?.sourceOrigin ?? null,
|
|
72
|
+
created_at: provenance?.createdAt ?? null,
|
|
61
73
|
updated_at: now,
|
|
62
74
|
}))
|
|
63
75
|
.execute();
|
|
76
|
+
// Reconcile topics: delete old, insert new
|
|
77
|
+
await this.relationalDb
|
|
78
|
+
.deleteFrom("graph_topics")
|
|
79
|
+
.where("document_id", "=", documentId)
|
|
80
|
+
.execute();
|
|
81
|
+
const topics = global.topics ?? [];
|
|
82
|
+
if (topics.length > 0) {
|
|
83
|
+
await this.relationalDb
|
|
84
|
+
.insertInto("graph_topics")
|
|
85
|
+
.values(topics.map((topic, idx) => {
|
|
86
|
+
const name = typeof topic === "string"
|
|
87
|
+
? topic
|
|
88
|
+
: (topic.name ?? "");
|
|
89
|
+
return {
|
|
90
|
+
id: `${documentId}-topic-${idx}`,
|
|
91
|
+
document_id: documentId,
|
|
92
|
+
name,
|
|
93
|
+
updated_at: now,
|
|
94
|
+
};
|
|
95
|
+
}))
|
|
96
|
+
.execute();
|
|
97
|
+
}
|
|
64
98
|
// Reconcile edges: delete old, insert new
|
|
65
99
|
await this.relationalDb
|
|
66
100
|
.deleteFrom("graph_edges")
|
|
@@ -82,6 +116,15 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
|
82
116
|
.execute();
|
|
83
117
|
}
|
|
84
118
|
console.log(`[GraphIndexer] Reconciled ${documentId}: ${links.length} edges`);
|
|
119
|
+
// Fire-and-forget embedding generation (don't block operation processing)
|
|
120
|
+
const text = [global.title, global.description, global.content]
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
.join(" ");
|
|
123
|
+
if (text.length > 0) {
|
|
124
|
+
generateEmbedding(text)
|
|
125
|
+
.then((emb) => upsertEmbedding(documentId, emb))
|
|
126
|
+
.catch((err) => console.warn(`[GraphIndexer] Embedding failed for ${documentId}:`, err));
|
|
127
|
+
}
|
|
85
128
|
}
|
|
86
129
|
catch (err) {
|
|
87
130
|
console.error(`[GraphIndexer] Error reconciling document ${documentId}:`, err);
|
|
@@ -89,17 +132,18 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
|
89
132
|
}
|
|
90
133
|
}
|
|
91
134
|
async onDisconnect() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
catch (err) {
|
|
98
|
-
console.error(`[GraphIndexer] Error cleaning up:`, err);
|
|
99
|
-
}
|
|
135
|
+
// Intentionally no-op: preserve indexed data across restarts.
|
|
136
|
+
// The reactor does not replay historical operations on reconnect,
|
|
137
|
+
// so wiping tables here would leave the index permanently empty
|
|
138
|
+
// until new operations arrive. Use knowledgeGraphReindex mutation
|
|
139
|
+
// to rebuild if needed.
|
|
100
140
|
}
|
|
101
141
|
async deleteNode(documentId) {
|
|
102
142
|
try {
|
|
143
|
+
await this.relationalDb
|
|
144
|
+
.deleteFrom("graph_topics")
|
|
145
|
+
.where("document_id", "=", documentId)
|
|
146
|
+
.execute();
|
|
103
147
|
await this.relationalDb
|
|
104
148
|
.deleteFrom("graph_edges")
|
|
105
149
|
.where((eb) => eb.or([
|
|
@@ -111,6 +155,7 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
|
|
|
111
155
|
.deleteFrom("graph_nodes")
|
|
112
156
|
.where("document_id", "=", documentId)
|
|
113
157
|
.execute();
|
|
158
|
+
deleteEmbedding(documentId).catch((err) => console.warn(`[GraphIndexer] Embedding delete failed for ${documentId}:`, err));
|
|
114
159
|
console.log(`[GraphIndexer] Deleted node ${documentId}`);
|
|
115
160
|
}
|
|
116
161
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,wBAAsB,EAAE,CAAC,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,wBAAsB,EAAE,CAAC,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmF9D;AAED,wBAAsB,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhE"}
|
|
@@ -39,8 +39,43 @@ export async function up(db) {
|
|
|
39
39
|
.column("status")
|
|
40
40
|
.ifNotExists()
|
|
41
41
|
.execute();
|
|
42
|
+
// --- Phase 1: topics, content, provenance ---
|
|
43
|
+
// Add new columns to graph_nodes (wrap each in try/catch for idempotency)
|
|
44
|
+
for (const col of ["content", "author", "source_origin", "created_at"]) {
|
|
45
|
+
try {
|
|
46
|
+
await db.schema
|
|
47
|
+
.alterTable("graph_nodes")
|
|
48
|
+
.addColumn(col, col === "content" ? "text" : "varchar(1024)")
|
|
49
|
+
.execute();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// column likely already exists — ignore
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Create graph_topics table
|
|
56
|
+
await db.schema
|
|
57
|
+
.createTable("graph_topics")
|
|
58
|
+
.addColumn("id", "varchar(255)", (col) => col.primaryKey())
|
|
59
|
+
.addColumn("document_id", "varchar(255)", (col) => col.notNull())
|
|
60
|
+
.addColumn("name", "varchar(512)", (col) => col.notNull())
|
|
61
|
+
.addColumn("updated_at", "varchar(50)", (col) => col.notNull())
|
|
62
|
+
.ifNotExists()
|
|
63
|
+
.execute();
|
|
64
|
+
await db.schema
|
|
65
|
+
.createIndex("idx_graph_topics_document_id")
|
|
66
|
+
.on("graph_topics")
|
|
67
|
+
.column("document_id")
|
|
68
|
+
.ifNotExists()
|
|
69
|
+
.execute();
|
|
70
|
+
await db.schema
|
|
71
|
+
.createIndex("idx_graph_topics_name")
|
|
72
|
+
.on("graph_topics")
|
|
73
|
+
.column("name")
|
|
74
|
+
.ifNotExists()
|
|
75
|
+
.execute();
|
|
42
76
|
}
|
|
43
77
|
export async function down(db) {
|
|
78
|
+
await db.schema.dropTable("graph_topics").ifExists().execute();
|
|
44
79
|
await db.schema.dropTable("graph_edges").ifExists().execute();
|
|
45
80
|
await db.schema.dropTable("graph_nodes").ifExists().execute();
|
|
46
81
|
}
|
|
@@ -7,6 +7,10 @@ export interface GraphNodeResult {
|
|
|
7
7
|
description: string | null;
|
|
8
8
|
noteType: string | null;
|
|
9
9
|
status: string | null;
|
|
10
|
+
content: string | null;
|
|
11
|
+
author: string | null;
|
|
12
|
+
sourceOrigin: string | null;
|
|
13
|
+
createdAt: string | null;
|
|
10
14
|
updatedAt: string;
|
|
11
15
|
}
|
|
12
16
|
export interface GraphEdgeResult {
|
|
@@ -27,6 +31,15 @@ export interface ConnectionResult {
|
|
|
27
31
|
depth: number;
|
|
28
32
|
viaLinkType: string | null;
|
|
29
33
|
}
|
|
34
|
+
export interface TopicStatsResult {
|
|
35
|
+
name: string;
|
|
36
|
+
noteCount: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RelatedByTopicResult {
|
|
39
|
+
node: GraphNodeResult;
|
|
40
|
+
sharedTopics: string[];
|
|
41
|
+
sharedTopicCount: number;
|
|
42
|
+
}
|
|
30
43
|
export declare function createGraphQuery(db: Kysely<DB>): {
|
|
31
44
|
allNodes(): Promise<GraphNodeResult[]>;
|
|
32
45
|
allEdges(): Promise<GraphEdgeResult[]>;
|
|
@@ -45,5 +58,13 @@ export declare function createGraphQuery(db: Kysely<DB>): {
|
|
|
45
58
|
sharedTarget: GraphNodeResult;
|
|
46
59
|
}>>;
|
|
47
60
|
bridges(): Promise<GraphNodeResult[]>;
|
|
61
|
+
topicStats(): Promise<TopicStatsResult[]>;
|
|
62
|
+
topicsForNode(documentId: string): Promise<string[]>;
|
|
63
|
+
nodesByTopic(topic: string): Promise<GraphNodeResult[]>;
|
|
64
|
+
relatedByTopic(documentId: string, limit?: number): Promise<RelatedByTopicResult[]>;
|
|
65
|
+
fullSearch(query: string, limit?: number): Promise<GraphNodeResult[]>;
|
|
66
|
+
nodesByAuthor(author: string): Promise<GraphNodeResult[]>;
|
|
67
|
+
nodesByOrigin(origin: string): Promise<GraphNodeResult[]>;
|
|
68
|
+
recentNodes(limit?: number, since?: string): Promise<GraphNodeResult[]>;
|
|
48
69
|
};
|
|
49
70
|
//# sourceMappingURL=query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../processors/graph-indexer/query.ts"],"names":[],"mappings":"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"}
|