@powerhousedao/knowledge-note 1.0.3 → 1.0.5

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.
Files changed (30) hide show
  1. package/README.md +359 -122
  2. package/dist/editors/knowledge-vault/components/DriveExplorer.d.ts.map +1 -1
  3. package/dist/editors/knowledge-vault/components/DriveExplorer.js +8 -2
  4. package/dist/editors/knowledge-vault/components/SearchView.d.ts +2 -0
  5. package/dist/editors/knowledge-vault/components/SearchView.d.ts.map +1 -0
  6. package/dist/editors/knowledge-vault/components/SearchView.js +96 -0
  7. package/dist/editors/knowledge-vault/hooks/use-graph-search.d.ts +25 -0
  8. package/dist/editors/knowledge-vault/hooks/use-graph-search.d.ts.map +1 -0
  9. package/dist/editors/knowledge-vault/hooks/use-graph-search.js +207 -0
  10. package/dist/package.json +2 -1
  11. package/dist/processors/graph-indexer/embedder.d.ts +5 -0
  12. package/dist/processors/graph-indexer/embedder.d.ts.map +1 -0
  13. package/dist/processors/graph-indexer/embedder.js +27 -0
  14. package/dist/processors/graph-indexer/embedding-store.d.ts +11 -0
  15. package/dist/processors/graph-indexer/embedding-store.d.ts.map +1 -0
  16. package/dist/processors/graph-indexer/embedding-store.js +70 -0
  17. package/dist/processors/graph-indexer/index.d.ts.map +1 -1
  18. package/dist/processors/graph-indexer/index.js +48 -0
  19. package/dist/processors/graph-indexer/migrations.d.ts.map +1 -1
  20. package/dist/processors/graph-indexer/migrations.js +35 -0
  21. package/dist/processors/graph-indexer/query.d.ts +21 -0
  22. package/dist/processors/graph-indexer/query.d.ts.map +1 -1
  23. package/dist/processors/graph-indexer/query.js +154 -13
  24. package/dist/processors/graph-indexer/schema.d.ts +11 -0
  25. package/dist/processors/graph-indexer/schema.d.ts.map +1 -1
  26. package/dist/style.css +63 -0
  27. package/dist/subgraphs/knowledge-graph/subgraph.d.ts +321 -12
  28. package/dist/subgraphs/knowledge-graph/subgraph.d.ts.map +1 -1
  29. package/dist/subgraphs/knowledge-graph/subgraph.js +237 -15
  30. package/package.json +2 -1
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { setSelectedNode } from "@powerhousedao/reactor-browser";
4
+ import { useGraphSearch, } from "../hooks/use-graph-search.js";
5
+ /* ------------------------------------------------------------------ */
6
+ /* Constants */
7
+ /* ------------------------------------------------------------------ */
8
+ const STATUS_COLORS = {
9
+ DRAFT: {
10
+ background: "rgba(245, 158, 11, 0.2)",
11
+ color: "rgba(252, 211, 77, 1)",
12
+ borderColor: "rgba(245, 158, 11, 0.3)",
13
+ },
14
+ IN_REVIEW: {
15
+ background: "rgba(59, 130, 246, 0.2)",
16
+ color: "rgba(147, 197, 253, 1)",
17
+ borderColor: "rgba(59, 130, 246, 0.3)",
18
+ },
19
+ CANONICAL: {
20
+ background: "rgba(16, 185, 129, 0.2)",
21
+ color: "rgba(110, 231, 183, 1)",
22
+ borderColor: "rgba(16, 185, 129, 0.3)",
23
+ },
24
+ ARCHIVED: {
25
+ background: "var(--bai-hover)",
26
+ color: "var(--bai-text-muted)",
27
+ borderColor: "var(--bai-border)",
28
+ },
29
+ };
30
+ function similarityColor(score) {
31
+ if (score >= 0.8)
32
+ return "#10b981";
33
+ if (score >= 0.6)
34
+ return "#f59e0b";
35
+ return "#6b7280";
36
+ }
37
+ /* ------------------------------------------------------------------ */
38
+ /* Component */
39
+ /* ------------------------------------------------------------------ */
40
+ export function SearchView() {
41
+ const { query, setQuery, results, topics, loading, error, searchMode, setSearchMode, } = useGraphSearch();
42
+ return (_jsxs("div", { className: "flex h-full flex-col p-4", children: [_jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsxs("div", { className: "relative flex-1", style: { color: "var(--bai-text-muted)" }, children: [_jsxs("svg", { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.35-4.35" })] }), _jsx("input", { type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search knowledge vault...", className: "w-full rounded-lg border py-2.5 pl-10 pr-10 text-sm outline-none transition-colors", style: {
43
+ backgroundColor: "var(--bai-surface)",
44
+ borderColor: "var(--bai-border)",
45
+ color: "var(--bai-text)",
46
+ } }), query && (_jsx("button", { type: "button", onClick: () => setQuery(""), className: "absolute right-3 top-1/2 -translate-y-1/2 rounded p-0.5 transition-colors hover:opacity-80", style: { color: "var(--bai-text-muted)" }, children: _jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }))] }), _jsx(ModeToggle, { mode: searchMode, onChange: setSearchMode })] }), query.trim() && (_jsxs("div", { className: "mb-3 flex items-center gap-2 text-xs", style: { color: "var(--bai-text-muted)" }, children: [loading ? (_jsx("span", { children: "Searching..." })) : (_jsxs(_Fragment, { children: [_jsxs("span", { children: [results.length, " result", results.length !== 1 ? "s" : ""] }), searchMode === "semantic" && results.length > 0 && (_jsx("span", { style: { opacity: 0.6 }, children: "\u2014 match % shows how closely each note relates to your query by meaning" }))] })), error && _jsx("span", { style: { color: "#ef4444" }, children: error })] })), _jsx("div", { className: "flex-1 overflow-auto", children: !query.trim() ? (_jsx(EmptyState, { topics: topics, onTopicClick: setQuery })) : (_jsx(ResultList, { results: results, searchMode: searchMode })) })] }));
47
+ }
48
+ /* ------------------------------------------------------------------ */
49
+ /* Mode toggle */
50
+ /* ------------------------------------------------------------------ */
51
+ function ModeToggle({ mode, onChange, }) {
52
+ return (_jsxs("div", { className: "flex shrink-0 overflow-hidden rounded-lg border text-xs", style: { borderColor: "var(--bai-border)" }, children: [_jsx("button", { type: "button", onClick: () => onChange("semantic"), className: "px-3 py-2 transition-colors", style: {
53
+ backgroundColor: mode === "semantic" ? "var(--bai-accent)" : "var(--bai-surface)",
54
+ color: mode === "semantic" ? "#fff" : "var(--bai-text-muted)",
55
+ }, children: "Semantic" }), _jsx("button", { type: "button", onClick: () => onChange("keyword"), className: "px-3 py-2 transition-colors", style: {
56
+ backgroundColor: mode === "keyword" ? "var(--bai-accent)" : "var(--bai-surface)",
57
+ color: mode === "keyword" ? "#fff" : "var(--bai-text-muted)",
58
+ }, children: "Keyword" })] }));
59
+ }
60
+ /* ------------------------------------------------------------------ */
61
+ /* Empty state */
62
+ /* ------------------------------------------------------------------ */
63
+ function EmptyState({ topics, onTopicClick, }) {
64
+ const [showTopics, setShowTopics] = useState(false);
65
+ return (_jsxs("div", { className: "flex flex-col items-center pt-16", children: [_jsxs("svg", { className: "mb-4 h-12 w-12", style: { color: "var(--bai-text-faint)", opacity: 0.5 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.35-4.35" })] }), _jsx("h2", { className: "mb-2 text-lg font-semibold", style: { color: "var(--bai-text)" }, children: "Search the Knowledge Vault" }), _jsx("p", { className: "mb-6 max-w-md text-center text-sm leading-relaxed", style: { color: "var(--bai-text-muted)" }, children: "Ask a question in natural language or type keywords. Semantic search understands meaning \u2014 try \"how does storage work?\" or \"legal setup for organizations\"." }), topics.length > 0 && (_jsxs("div", { className: "flex flex-col items-center", children: [_jsx("button", { type: "button", onClick: () => setShowTopics((s) => !s), className: "mb-4 rounded-lg border px-5 py-2.5 text-sm font-medium transition-colors", style: {
66
+ borderColor: "var(--bai-border)",
67
+ color: "var(--bai-accent)",
68
+ backgroundColor: "var(--bai-surface)",
69
+ }, children: showTopics ? "Hide Topics" : `Explore ${topics.length} Topics` }), showTopics && (_jsx("div", { className: "max-w-2xl", children: _jsx("div", { className: "flex flex-wrap justify-center gap-2", children: topics.map((t) => (_jsxs("button", { type: "button", onClick: () => onTopicClick(t.name), className: "rounded-full px-3 py-1.5 text-xs transition-colors", style: {
70
+ backgroundColor: "var(--bai-hover)",
71
+ color: "var(--bai-accent)",
72
+ }, children: [t.name, _jsx("span", { className: "ml-1.5 opacity-50", style: { color: "var(--bai-text-muted)" }, children: t.noteCount })] }, t.name))) }) }))] }))] }));
73
+ }
74
+ /* ------------------------------------------------------------------ */
75
+ /* Result list */
76
+ /* ------------------------------------------------------------------ */
77
+ function ResultList({ results, searchMode, }) {
78
+ if (results.length === 0) {
79
+ return (_jsx("div", { className: "py-12 text-center text-sm", style: { color: "var(--bai-text-muted)" }, children: "No results found" }));
80
+ }
81
+ return (_jsx("div", { className: "space-y-2", children: results.map((result) => (_jsx(ResultCard, { result: result, showSimilarity: searchMode === "semantic" }, result.documentId))) }));
82
+ }
83
+ /* ------------------------------------------------------------------ */
84
+ /* Result card */
85
+ /* ------------------------------------------------------------------ */
86
+ function ResultCard({ result, showSimilarity, }) {
87
+ const status = result.status ?? "DRAFT";
88
+ const badgeStyle = STATUS_COLORS[status] ?? STATUS_COLORS.DRAFT;
89
+ return (_jsxs("button", { type: "button", onClick: () => setSelectedNode(result.documentId), className: "group flex w-full items-start gap-3 rounded-xl border p-4 text-left transition-all border-[var(--bai-border)] bg-[var(--bai-surface)] hover:border-[var(--bai-accent)] hover:bg-[var(--bai-hover)]", children: [showSimilarity && result.similarity != null && (_jsxs("div", { className: "shrink-0 rounded-md px-2 py-1 text-center font-mono text-xs font-bold", title: "Semantic similarity \u2014 how closely this note's meaning matches your query (higher is better)", style: {
90
+ color: similarityColor(result.similarity),
91
+ backgroundColor: `${similarityColor(result.similarity)}15`,
92
+ }, children: [(result.similarity * 100).toFixed(0), "%"] })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "mb-1 flex items-start justify-between gap-2", children: [_jsx("h4", { className: "truncate text-sm font-semibold text-[var(--bai-text)] group-hover:text-[var(--bai-accent)]", children: result.title ?? "Untitled" }), _jsx("span", { className: "shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium", style: badgeStyle, children: status.replace("_", " ") })] }), result.description && (_jsx("p", { className: "mb-2 line-clamp-2 text-xs leading-relaxed", style: { color: "var(--bai-text-muted)" }, children: result.description })), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [result.noteType && (_jsx("span", { className: "rounded px-1.5 py-0.5 text-[10px] font-medium", style: {
93
+ background: "var(--bai-hover)",
94
+ color: "var(--bai-text-muted)",
95
+ }, children: result.noteType })), result.topics?.slice(0, 4).map((topic) => (_jsxs("span", { className: "text-[10px]", style: { color: "var(--bai-accent)", opacity: 0.6 }, children: ["#", topic] }, topic)))] })] }), _jsx("svg", { className: "mt-1 h-4 w-4 shrink-0 opacity-0 transition-opacity group-hover:opacity-60", style: { color: "var(--bai-text-muted)" }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M9 18l6-6-6-6" }) })] }));
96
+ }
@@ -0,0 +1,25 @@
1
+ export type SearchResult = {
2
+ documentId: string;
3
+ title: string | null;
4
+ description: string | null;
5
+ noteType: string | null;
6
+ status: string | null;
7
+ topics: string[];
8
+ similarity?: number;
9
+ };
10
+ export type TopicInfo = {
11
+ name: string;
12
+ noteCount: number;
13
+ };
14
+ export type SearchMode = "semantic" | "keyword";
15
+ export declare function useGraphSearch(): {
16
+ query: string;
17
+ setQuery: (q: string) => void;
18
+ results: SearchResult[];
19
+ topics: TopicInfo[];
20
+ loading: boolean;
21
+ error: string | null;
22
+ searchMode: SearchMode;
23
+ setSearchMode: (m: SearchMode) => void;
24
+ };
25
+ //# sourceMappingURL=use-graph-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-graph-search.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/hooks/use-graph-search.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAkIhD,wBAAgB,cAAc;;kBAgBtB,MAAM;;;;;;uBAQN,UAAU;EAoGjB"}
@@ -0,0 +1,207 @@
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
+ // Known Connect → Switchboard domain mappings for deployed environments
22
+ const DOMAIN_MAP = {
23
+ "connect-dev.powerhouse.xyz": "https://switchboard-dev.powerhouse.xyz/graphql/knowledgeGraph",
24
+ };
25
+ function resolveEndpoint() {
26
+ // Explicit override via env var
27
+ const envUrl = typeof import.meta !== "undefined" &&
28
+ import.meta.env?.VITE_SUBGRAPH_URL;
29
+ if (envUrl)
30
+ return envUrl;
31
+ // Check known Connect → Switchboard domain mappings
32
+ const hostname = globalThis.window?.location?.hostname;
33
+ if (hostname && DOMAIN_MAP[hostname]) {
34
+ return DOMAIN_MAP[hostname];
35
+ }
36
+ // Vite dev server proxying to local reactor
37
+ const port = globalThis.window?.location?.port;
38
+ if (port === "3000" || port === "3001") {
39
+ return `http://localhost:4001${SUBGRAPH_PATH}`;
40
+ }
41
+ // Same-origin (Connect production)
42
+ return SUBGRAPH_PATH;
43
+ }
44
+ async function graphqlFetch(endpoint, query, variables) {
45
+ try {
46
+ const res = await fetch(endpoint, {
47
+ method: "POST",
48
+ headers: { "Content-Type": "application/json" },
49
+ body: JSON.stringify({ query, variables }),
50
+ });
51
+ if (!res.ok)
52
+ return null;
53
+ const json = (await res.json());
54
+ if (json.errors) {
55
+ console.warn("[useGraphSearch] GraphQL errors:", json.errors);
56
+ }
57
+ return json.data ?? null;
58
+ }
59
+ catch (err) {
60
+ console.warn("[useGraphSearch] Fetch failed:", err);
61
+ return null;
62
+ }
63
+ }
64
+ /* ------------------------------------------------------------------ */
65
+ /* Queries */
66
+ /* ------------------------------------------------------------------ */
67
+ const SEMANTIC_SEARCH_QUERY = `
68
+ query SemanticSearch($driveId: ID!, $query: String!, $limit: Int) {
69
+ knowledgeGraphSemanticSearch(driveId: $driveId, query: $query, limit: $limit) {
70
+ node { documentId title description noteType status topics }
71
+ similarity
72
+ }
73
+ }
74
+ `;
75
+ const KEYWORD_SEARCH_QUERY = `
76
+ query FullSearch($driveId: ID!, $query: String!, $limit: Int) {
77
+ knowledgeGraphFullSearch(driveId: $driveId, query: $query, limit: $limit) {
78
+ documentId title description noteType status topics
79
+ }
80
+ }
81
+ `;
82
+ const TOPICS_QUERY = `
83
+ query Topics($driveId: ID!) {
84
+ knowledgeGraphTopics(driveId: $driveId) { name noteCount }
85
+ }
86
+ `;
87
+ /* ------------------------------------------------------------------ */
88
+ /* Hook */
89
+ /* ------------------------------------------------------------------ */
90
+ const STORAGE_KEY = "bai-search-state";
91
+ function loadSearchState() {
92
+ try {
93
+ const raw = sessionStorage.getItem(STORAGE_KEY);
94
+ if (raw) {
95
+ const parsed = JSON.parse(raw);
96
+ return {
97
+ query: parsed.query ?? "",
98
+ mode: parsed.mode === "keyword" ? "keyword" : "semantic",
99
+ };
100
+ }
101
+ }
102
+ catch {
103
+ // ignore
104
+ }
105
+ return { query: "", mode: "semantic" };
106
+ }
107
+ function saveSearchState(query, mode) {
108
+ try {
109
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ query, mode }));
110
+ }
111
+ catch {
112
+ // ignore
113
+ }
114
+ }
115
+ export function useGraphSearch() {
116
+ const driveId = useSelectedDriveId();
117
+ const saved = useRef(loadSearchState());
118
+ const [query, setQueryRaw] = useState(saved.current.query);
119
+ const [results, setResults] = useState([]);
120
+ const [topics, setTopics] = useState([]);
121
+ const [loading, setLoading] = useState(false);
122
+ const [error, setError] = useState(null);
123
+ const [searchMode, setSearchModeRaw] = useState(saved.current.mode);
124
+ const debounceRef = useRef(null);
125
+ const endpoint = useMemo(() => resolveEndpoint(), []);
126
+ // Persist query and mode to sessionStorage
127
+ const setQuery = useCallback((q) => {
128
+ setQueryRaw(q);
129
+ saveSearchState(q, searchMode);
130
+ }, [searchMode]);
131
+ const setSearchMode = useCallback((m) => {
132
+ setSearchModeRaw(m);
133
+ saveSearchState(query, m);
134
+ }, [query]);
135
+ // Fetch topics on mount for empty-state overview
136
+ useEffect(() => {
137
+ if (!driveId)
138
+ return;
139
+ graphqlFetch(endpoint, TOPICS_QUERY, { driveId }).then((data) => {
140
+ if (data?.knowledgeGraphTopics) {
141
+ setTopics(data.knowledgeGraphTopics);
142
+ }
143
+ });
144
+ }, [driveId, endpoint]);
145
+ // Debounced search
146
+ const executeSearch = useCallback(async (q, mode) => {
147
+ if (!driveId || !q.trim()) {
148
+ setResults([]);
149
+ setLoading(false);
150
+ return;
151
+ }
152
+ setLoading(true);
153
+ setError(null);
154
+ if (mode === "semantic") {
155
+ const data = await graphqlFetch(endpoint, SEMANTIC_SEARCH_QUERY, { driveId, query: q, limit: 20 });
156
+ if (data?.knowledgeGraphSemanticSearch) {
157
+ setResults(data.knowledgeGraphSemanticSearch.map((r) => ({
158
+ ...r.node,
159
+ similarity: r.similarity,
160
+ })));
161
+ }
162
+ else {
163
+ setResults([]);
164
+ setError("Semantic search unavailable. Try keyword mode.");
165
+ }
166
+ }
167
+ else {
168
+ const data = await graphqlFetch(endpoint, KEYWORD_SEARCH_QUERY, { driveId, query: q, limit: 20 });
169
+ if (data?.knowledgeGraphFullSearch) {
170
+ setResults(data.knowledgeGraphFullSearch);
171
+ }
172
+ else {
173
+ setResults([]);
174
+ setError("Search failed.");
175
+ }
176
+ }
177
+ setLoading(false);
178
+ }, [driveId, endpoint]);
179
+ // Trigger debounced search on query or mode change
180
+ useEffect(() => {
181
+ if (debounceRef.current)
182
+ clearTimeout(debounceRef.current);
183
+ if (!query.trim()) {
184
+ setResults([]);
185
+ setLoading(false);
186
+ return;
187
+ }
188
+ setLoading(true);
189
+ debounceRef.current = setTimeout(() => {
190
+ void executeSearch(query, searchMode);
191
+ }, DEBOUNCE_MS);
192
+ return () => {
193
+ if (debounceRef.current)
194
+ clearTimeout(debounceRef.current);
195
+ };
196
+ }, [query, searchMode, executeSearch]);
197
+ return {
198
+ query,
199
+ setQuery,
200
+ results,
201
+ topics,
202
+ loading,
203
+ error,
204
+ searchMode,
205
+ setSearchMode,
206
+ };
207
+ }
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.3",
4
+ "version": "1.0.5",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "files": [
@@ -107,6 +107,7 @@
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",
@@ -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;AAEtC,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;IAqGV,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAQrB,UAAU;CAoBzB"}
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);
@@ -97,6 +140,10 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
97
140
  }
98
141
  async deleteNode(documentId) {
99
142
  try {
143
+ await this.relationalDb
144
+ .deleteFrom("graph_topics")
145
+ .where("document_id", "=", documentId)
146
+ .execute();
100
147
  await this.relationalDb
101
148
  .deleteFrom("graph_edges")
102
149
  .where((eb) => eb.or([
@@ -108,6 +155,7 @@ export class GraphIndexerProcessor extends RelationalDbProcessor {
108
155
  .deleteFrom("graph_nodes")
109
156
  .where("document_id", "=", documentId)
110
157
  .execute();
158
+ deleteEmbedding(documentId).catch((err) => console.warn(`[GraphIndexer] Embedding delete failed for ${documentId}:`, err));
111
159
  console.log(`[GraphIndexer] Deleted node ${documentId}`);
112
160
  }
113
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,CA6C9D;AAED,wBAAsB,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhE"}
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
  }