@trustgraph/react-state 1.7.1 → 1.7.3
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/dist/index.cjs +337 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +338 -236
- package/dist/index.esm.js.map +1 -1
- package/dist/state/chat-session.d.ts.map +1 -1
- package/dist/state/explainability.d.ts +8 -5
- package/dist/state/explainability.d.ts.map +1 -1
- package/dist/state/provenance.d.ts +3 -2
- package/dist/state/provenance.d.ts.map +1 -1
- package/dist/utils/explainability.d.ts +62 -34
- package/dist/utils/explainability.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1922,9 +1922,28 @@ const useExplainabilityStore = zustand.create()((set, get) => ({
|
|
|
1922
1922
|
/**
|
|
1923
1923
|
* Explainability utilities for parsing and structuring explain events
|
|
1924
1924
|
*/
|
|
1925
|
+
// Predicates not yet in client library
|
|
1926
|
+
const TG_ACTION = client.TG + "action";
|
|
1927
|
+
const TG_ARGUMENTS = client.TG + "arguments";
|
|
1928
|
+
const TG_SUBAGENT_GOAL = client.TG + "subagentGoal";
|
|
1929
|
+
const TG_CONCEPT = client.TG + "concept";
|
|
1930
|
+
const TG_ENTITY = client.TG + "entity";
|
|
1931
|
+
// RDF type URIs
|
|
1932
|
+
const TG_AGENT_QUESTION = client.TG + "AgentQuestion";
|
|
1933
|
+
const TG_ANALYSIS = client.TG + "Analysis";
|
|
1934
|
+
const TG_TOOL_USE = client.TG + "ToolUse";
|
|
1935
|
+
const TG_OBSERVATION = client.TG + "Observation";
|
|
1936
|
+
const TG_THOUGHT_TYPE = client.TG + "Thought";
|
|
1937
|
+
const TG_REFLECTION_TYPE = client.TG + "Reflection";
|
|
1938
|
+
const TG_CONCLUSION = client.TG + "Conclusion";
|
|
1939
|
+
const TG_FINDING = client.TG + "Finding";
|
|
1940
|
+
const TG_DECOMPOSITION = client.TG + "Decomposition";
|
|
1941
|
+
const TG_GROUNDING = client.TG + "Grounding";
|
|
1942
|
+
const TG_EXPLORATION_TYPE = client.TG + "Exploration";
|
|
1943
|
+
const TG_FOCUS_TYPE = client.TG + "Focus";
|
|
1944
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
1925
1945
|
/**
|
|
1926
|
-
* Extract event type from explainId URI
|
|
1927
|
-
* e.g., "urn:trustgraph:question:abc123" → "question"
|
|
1946
|
+
* Extract event type from explainId URI (graph-rag fallback)
|
|
1928
1947
|
*/
|
|
1929
1948
|
function getEventType(explainId) {
|
|
1930
1949
|
if (explainId.includes("question"))
|
|
@@ -1937,9 +1956,6 @@ function getEventType(explainId) {
|
|
|
1937
1956
|
return "synthesis";
|
|
1938
1957
|
return "unknown";
|
|
1939
1958
|
}
|
|
1940
|
-
/**
|
|
1941
|
-
* Get term value from a Term object
|
|
1942
|
-
*/
|
|
1943
1959
|
function getTermValue$1(term) {
|
|
1944
1960
|
if (!term)
|
|
1945
1961
|
return "";
|
|
@@ -1948,7 +1964,6 @@ function getTermValue$1(term) {
|
|
|
1948
1964
|
if (term.t === "l")
|
|
1949
1965
|
return term.v || "";
|
|
1950
1966
|
if (term.t === "t" && term.tr) {
|
|
1951
|
-
// Quoted triple - return a serialized form
|
|
1952
1967
|
const s = getTermValue$1(term.tr.s);
|
|
1953
1968
|
const p = getTermValue$1(term.tr.p);
|
|
1954
1969
|
const o = getTermValue$1(term.tr.o);
|
|
@@ -1956,9 +1971,6 @@ function getTermValue$1(term) {
|
|
|
1956
1971
|
}
|
|
1957
1972
|
return "";
|
|
1958
1973
|
}
|
|
1959
|
-
/**
|
|
1960
|
-
* Extract quoted triple from a Term
|
|
1961
|
-
*/
|
|
1962
1974
|
function extractQuotedTriple(term) {
|
|
1963
1975
|
if (term.t === "t" && term.tr) {
|
|
1964
1976
|
return {
|
|
@@ -1969,48 +1981,102 @@ function extractQuotedTriple(term) {
|
|
|
1969
1981
|
}
|
|
1970
1982
|
return null;
|
|
1971
1983
|
}
|
|
1984
|
+
// ── RDF type detection ──────────────────────────────────────────────
|
|
1985
|
+
/**
|
|
1986
|
+
* Map RDF type URI → event type (first match wins).
|
|
1987
|
+
* Agent-specific types only — shared types like tg:Synthesis and
|
|
1988
|
+
* tg:Answer are excluded to avoid catching graph-rag events.
|
|
1989
|
+
* Grounding, Exploration, Focus are included since they now carry
|
|
1990
|
+
* embedded triples with their RDF type.
|
|
1991
|
+
*/
|
|
1992
|
+
const RDF_TYPE_CHECKS = [
|
|
1993
|
+
[TG_AGENT_QUESTION, "agent-question"],
|
|
1994
|
+
[TG_DECOMPOSITION, "decomposition"],
|
|
1995
|
+
[TG_ANALYSIS, "analysis"],
|
|
1996
|
+
[TG_TOOL_USE, "analysis"],
|
|
1997
|
+
[TG_OBSERVATION, "reflection"],
|
|
1998
|
+
[TG_THOUGHT_TYPE, "reflection"],
|
|
1999
|
+
[TG_REFLECTION_TYPE, "reflection"],
|
|
2000
|
+
[TG_CONCLUSION, "conclusion"],
|
|
2001
|
+
[TG_FINDING, "conclusion"],
|
|
2002
|
+
[TG_GROUNDING, "grounding"],
|
|
2003
|
+
[TG_EXPLORATION_TYPE, "exploration"],
|
|
2004
|
+
[TG_FOCUS_TYPE, "focus"],
|
|
2005
|
+
];
|
|
2006
|
+
/**
|
|
2007
|
+
* Detect event type from RDF types in embedded triples.
|
|
2008
|
+
*/
|
|
2009
|
+
function getEventTypeFromTriples(triples) {
|
|
2010
|
+
const types = new Set();
|
|
2011
|
+
for (const t of triples) {
|
|
2012
|
+
if (getTermValue$1(t.p) === client.RDF_TYPE) {
|
|
2013
|
+
types.add(getTermValue$1(t.o));
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
for (const [typeUri, eventType] of RDF_TYPE_CHECKS) {
|
|
2017
|
+
if (types.has(typeUri))
|
|
2018
|
+
return eventType;
|
|
2019
|
+
}
|
|
2020
|
+
return "unknown";
|
|
2021
|
+
}
|
|
2022
|
+
/** Extract common fields (label, derivedFrom) from triples */
|
|
2023
|
+
function extractCommonFields(triples) {
|
|
2024
|
+
const derivedFrom = [];
|
|
2025
|
+
let label;
|
|
2026
|
+
for (const t of triples) {
|
|
2027
|
+
const p = getTermValue$1(t.p);
|
|
2028
|
+
const o = getTermValue$1(t.o);
|
|
2029
|
+
if (p === client.RDFS_LABEL && o)
|
|
2030
|
+
label = o;
|
|
2031
|
+
if (p === client.PROV_WAS_DERIVED_FROM && o)
|
|
2032
|
+
derivedFrom.push(o);
|
|
2033
|
+
}
|
|
2034
|
+
return { label, derivedFrom };
|
|
2035
|
+
}
|
|
1972
2036
|
/**
|
|
1973
|
-
*
|
|
2037
|
+
* Extract inline edges from triples (tg:edge quoted triples).
|
|
2038
|
+
* Returns SelectedEdge objects with URIs — labels resolved later.
|
|
1974
2039
|
*/
|
|
2040
|
+
function extractInlineEdges(triples) {
|
|
2041
|
+
const edges = [];
|
|
2042
|
+
for (const t of triples) {
|
|
2043
|
+
if (getTermValue$1(t.p) === client.TG_EDGE && t.o.t === "t" && t.o.tr) {
|
|
2044
|
+
const edge = extractQuotedTriple(t.o);
|
|
2045
|
+
if (edge) {
|
|
2046
|
+
edges.push({ edge });
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
return edges;
|
|
2051
|
+
}
|
|
2052
|
+
// ── Parsers ─────────────────────────────────────────────────────────
|
|
1975
2053
|
function parseQuestionTriples(explainId, explainGraph, triples) {
|
|
1976
|
-
const event = {
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
for (const triple of triples) {
|
|
1982
|
-
const p = getTermValue$1(triple.p);
|
|
1983
|
-
const o = getTermValue$1(triple.o);
|
|
1984
|
-
if (p === client.TG_QUERY) {
|
|
2054
|
+
const event = { type: "question", explainId, explainGraph };
|
|
2055
|
+
for (const t of triples) {
|
|
2056
|
+
const p = getTermValue$1(t.p);
|
|
2057
|
+
const o = getTermValue$1(t.o);
|
|
2058
|
+
if (p === client.TG_QUERY)
|
|
1985
2059
|
event.query = o;
|
|
1986
|
-
|
|
1987
|
-
else if (p === client.PROV_STARTED_AT_TIME) {
|
|
2060
|
+
else if (p === client.PROV_STARTED_AT_TIME)
|
|
1988
2061
|
event.timestamp = o;
|
|
1989
|
-
}
|
|
1990
2062
|
}
|
|
1991
2063
|
return event;
|
|
1992
2064
|
}
|
|
1993
|
-
/**
|
|
1994
|
-
* Parse triples for an exploration event
|
|
1995
|
-
*/
|
|
1996
2065
|
function parseExplorationTriples(explainId, explainGraph, triples) {
|
|
1997
|
-
const event = {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
const p = getTermValue$1(triple.p);
|
|
2004
|
-
const o = getTermValue$1(triple.o);
|
|
2005
|
-
if (p === client.TG_EDGE_COUNT) {
|
|
2066
|
+
const event = { type: "exploration", explainId, explainGraph };
|
|
2067
|
+
const entities = [];
|
|
2068
|
+
for (const t of triples) {
|
|
2069
|
+
const p = getTermValue$1(t.p);
|
|
2070
|
+
const o = getTermValue$1(t.o);
|
|
2071
|
+
if (p === client.TG_EDGE_COUNT)
|
|
2006
2072
|
event.edgeCount = parseInt(o, 10);
|
|
2007
|
-
|
|
2073
|
+
if (p === TG_ENTITY && o)
|
|
2074
|
+
entities.push(o);
|
|
2008
2075
|
}
|
|
2076
|
+
if (entities.length > 0)
|
|
2077
|
+
event.entities = entities;
|
|
2009
2078
|
return event;
|
|
2010
2079
|
}
|
|
2011
|
-
/**
|
|
2012
|
-
* Parse triples for a focus event
|
|
2013
|
-
*/
|
|
2014
2080
|
function parseFocusTriples(explainId, explainGraph, triples) {
|
|
2015
2081
|
const event = {
|
|
2016
2082
|
type: "focus",
|
|
@@ -2018,54 +2084,135 @@ function parseFocusTriples(explainId, explainGraph, triples) {
|
|
|
2018
2084
|
explainGraph,
|
|
2019
2085
|
edgeSelectionUris: [],
|
|
2020
2086
|
};
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
const
|
|
2087
|
+
// Collect edge selection URIs (old format)
|
|
2088
|
+
for (const t of triples) {
|
|
2089
|
+
const p = getTermValue$1(t.p);
|
|
2090
|
+
const o = getTermValue$1(t.o);
|
|
2024
2091
|
if (p === client.TG_SELECTED_EDGE && typeof o === "string") {
|
|
2025
2092
|
event.edgeSelectionUris.push(o);
|
|
2026
2093
|
}
|
|
2027
2094
|
}
|
|
2095
|
+
// Extract inline edges (new format: tg:edge with quoted triples)
|
|
2096
|
+
const inlineEdges = extractInlineEdges(triples);
|
|
2097
|
+
if (inlineEdges.length > 0) {
|
|
2098
|
+
event.selectedEdges = inlineEdges;
|
|
2099
|
+
}
|
|
2028
2100
|
return event;
|
|
2029
2101
|
}
|
|
2030
|
-
/**
|
|
2031
|
-
* Parse triples for a synthesis event
|
|
2032
|
-
*/
|
|
2033
2102
|
function parseSynthesisTriples(explainId, explainGraph, triples) {
|
|
2034
|
-
const event = {
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
for (const triple of triples) {
|
|
2040
|
-
const p = getTermValue$1(triple.p);
|
|
2041
|
-
const o = getTermValue$1(triple.o);
|
|
2042
|
-
if (p === client.TG_CONTENT) {
|
|
2103
|
+
const event = { type: "synthesis", explainId, explainGraph };
|
|
2104
|
+
for (const t of triples) {
|
|
2105
|
+
const p = getTermValue$1(t.p);
|
|
2106
|
+
const o = getTermValue$1(t.o);
|
|
2107
|
+
if (p === client.TG_CONTENT)
|
|
2043
2108
|
event.contentLength = o.length;
|
|
2044
|
-
}
|
|
2045
2109
|
}
|
|
2046
2110
|
return event;
|
|
2047
2111
|
}
|
|
2048
|
-
/**
|
|
2049
|
-
* Parse triples for an edge selection entity
|
|
2050
|
-
*/
|
|
2051
2112
|
function parseEdgeSelectionTriples(triples) {
|
|
2052
2113
|
let edge = null;
|
|
2053
2114
|
let reasoning = null;
|
|
2054
2115
|
for (const triple of triples) {
|
|
2055
2116
|
const p = getTermValue$1(triple.p);
|
|
2056
|
-
if (p === client.TG_EDGE)
|
|
2117
|
+
if (p === client.TG_EDGE)
|
|
2057
2118
|
edge = extractQuotedTriple(triple.o);
|
|
2058
|
-
|
|
2059
|
-
else if (p === client.TG_REASONING) {
|
|
2119
|
+
else if (p === client.TG_REASONING)
|
|
2060
2120
|
reasoning = getTermValue$1(triple.o);
|
|
2061
|
-
}
|
|
2062
2121
|
}
|
|
2063
2122
|
return { edge, reasoning };
|
|
2064
2123
|
}
|
|
2124
|
+
function parseGroundingTriples(explainId, explainGraph, triples) {
|
|
2125
|
+
const { label, derivedFrom } = extractCommonFields(triples);
|
|
2126
|
+
const concepts = [];
|
|
2127
|
+
for (const t of triples) {
|
|
2128
|
+
const p = getTermValue$1(t.p);
|
|
2129
|
+
const o = getTermValue$1(t.o);
|
|
2130
|
+
if (p === TG_CONCEPT && o)
|
|
2131
|
+
concepts.push(o);
|
|
2132
|
+
}
|
|
2133
|
+
return { type: "grounding", explainId, explainGraph, label, concepts, derivedFrom };
|
|
2134
|
+
}
|
|
2135
|
+
function parseAgentQuestionTriples(explainId, explainGraph, triples) {
|
|
2136
|
+
const { label, derivedFrom } = extractCommonFields(triples);
|
|
2137
|
+
const event = {
|
|
2138
|
+
type: "agent-question", explainId, explainGraph, label, derivedFrom,
|
|
2139
|
+
};
|
|
2140
|
+
for (const t of triples) {
|
|
2141
|
+
const p = getTermValue$1(t.p);
|
|
2142
|
+
const o = getTermValue$1(t.o);
|
|
2143
|
+
if (p === client.TG_QUERY)
|
|
2144
|
+
event.query = o;
|
|
2145
|
+
if (p === client.PROV_STARTED_AT_TIME)
|
|
2146
|
+
event.timestamp = o;
|
|
2147
|
+
}
|
|
2148
|
+
return event;
|
|
2149
|
+
}
|
|
2150
|
+
function parseDecompositionTriples(explainId, explainGraph, triples) {
|
|
2151
|
+
const { label, derivedFrom } = extractCommonFields(triples);
|
|
2152
|
+
const goals = [];
|
|
2153
|
+
for (const t of triples) {
|
|
2154
|
+
const p = getTermValue$1(t.p);
|
|
2155
|
+
const o = getTermValue$1(t.o);
|
|
2156
|
+
if (p === TG_SUBAGENT_GOAL && o)
|
|
2157
|
+
goals.push(o);
|
|
2158
|
+
}
|
|
2159
|
+
return { type: "decomposition", explainId, explainGraph, label, goals, derivedFrom };
|
|
2160
|
+
}
|
|
2161
|
+
function parseAnalysisTriples(explainId, explainGraph, triples) {
|
|
2162
|
+
const { label, derivedFrom } = extractCommonFields(triples);
|
|
2163
|
+
const event = {
|
|
2164
|
+
type: "analysis", explainId, explainGraph, label, derivedFrom,
|
|
2165
|
+
};
|
|
2166
|
+
for (const t of triples) {
|
|
2167
|
+
const p = getTermValue$1(t.p);
|
|
2168
|
+
const o = getTermValue$1(t.o);
|
|
2169
|
+
if (p === TG_ACTION)
|
|
2170
|
+
event.action = o;
|
|
2171
|
+
if (p === TG_ARGUMENTS)
|
|
2172
|
+
event.arguments = o;
|
|
2173
|
+
}
|
|
2174
|
+
return event;
|
|
2175
|
+
}
|
|
2176
|
+
function parseReflectionTriples(explainId, explainGraph, triples) {
|
|
2177
|
+
const { label, derivedFrom } = extractCommonFields(triples);
|
|
2178
|
+
return { type: "reflection", explainId, explainGraph, label, derivedFrom };
|
|
2179
|
+
}
|
|
2180
|
+
function parseConclusionTriples(explainId, explainGraph, triples) {
|
|
2181
|
+
const { label, derivedFrom } = extractCommonFields(triples);
|
|
2182
|
+
return { type: "conclusion", explainId, explainGraph, label, derivedFrom };
|
|
2183
|
+
}
|
|
2184
|
+
// ── Unified parser ──────────────────────────────────────────────────
|
|
2065
2185
|
/**
|
|
2066
|
-
* Parse triples
|
|
2186
|
+
* Parse triples into a structured event.
|
|
2187
|
+
* Tries RDF type detection first, then URI patterns as fallback.
|
|
2188
|
+
* Returns null for events with no triples.
|
|
2067
2189
|
*/
|
|
2068
2190
|
function parseExplainTriples(explainId, explainGraph, triples) {
|
|
2191
|
+
if (triples.length === 0)
|
|
2192
|
+
return null;
|
|
2193
|
+
// Try RDF type detection first
|
|
2194
|
+
const rdfEventType = getEventTypeFromTriples(triples);
|
|
2195
|
+
if (rdfEventType !== "unknown") {
|
|
2196
|
+
switch (rdfEventType) {
|
|
2197
|
+
case "agent-question":
|
|
2198
|
+
return parseAgentQuestionTriples(explainId, explainGraph, triples);
|
|
2199
|
+
case "decomposition":
|
|
2200
|
+
return parseDecompositionTriples(explainId, explainGraph, triples);
|
|
2201
|
+
case "analysis":
|
|
2202
|
+
return parseAnalysisTriples(explainId, explainGraph, triples);
|
|
2203
|
+
case "reflection":
|
|
2204
|
+
return parseReflectionTriples(explainId, explainGraph, triples);
|
|
2205
|
+
case "conclusion":
|
|
2206
|
+
return parseConclusionTriples(explainId, explainGraph, triples);
|
|
2207
|
+
case "grounding":
|
|
2208
|
+
return parseGroundingTriples(explainId, explainGraph, triples);
|
|
2209
|
+
case "exploration":
|
|
2210
|
+
return parseExplorationTriples(explainId, explainGraph, triples);
|
|
2211
|
+
case "focus":
|
|
2212
|
+
return parseFocusTriples(explainId, explainGraph, triples);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
// Fall back to URI pattern detection (graph-rag events)
|
|
2069
2216
|
const eventType = getEventType(explainId);
|
|
2070
2217
|
switch (eventType) {
|
|
2071
2218
|
case "question":
|
|
@@ -2083,8 +2230,10 @@ function parseExplainTriples(explainId, explainGraph, triples) {
|
|
|
2083
2230
|
|
|
2084
2231
|
/**
|
|
2085
2232
|
* Hook for tracing provenance chains in the knowledge graph
|
|
2086
|
-
* Follows
|
|
2233
|
+
* Follows tg:contains to find subgraphs, then prov:wasDerivedFrom
|
|
2234
|
+
* to trace chunk → page → document chains
|
|
2087
2235
|
*/
|
|
2236
|
+
const TG_CONTAINS = client.TG + "contains";
|
|
2088
2237
|
/**
|
|
2089
2238
|
* Hook for tracing provenance chains
|
|
2090
2239
|
*/
|
|
@@ -2181,13 +2330,13 @@ const useProvenance = (options = {}) => {
|
|
|
2181
2330
|
}
|
|
2182
2331
|
}, [maxDepth, resolveLabel, queryDerivedFrom]);
|
|
2183
2332
|
/**
|
|
2184
|
-
*
|
|
2333
|
+
* Find subgraphs that contain an edge via tg:contains with a quoted triple
|
|
2185
2334
|
*/
|
|
2186
|
-
const
|
|
2335
|
+
const queryContainingSubgraphs = react.useCallback(async (s, p, o) => {
|
|
2187
2336
|
if (!isConnected())
|
|
2188
2337
|
return [];
|
|
2189
2338
|
try {
|
|
2190
|
-
// Build the quoted triple term
|
|
2339
|
+
// Build the quoted triple term for the edge
|
|
2191
2340
|
const quotedTriple = {
|
|
2192
2341
|
t: "t",
|
|
2193
2342
|
tr: {
|
|
@@ -2200,7 +2349,7 @@ const useProvenance = (options = {}) => {
|
|
|
2200
2349
|
};
|
|
2201
2350
|
const triples = await socket
|
|
2202
2351
|
.flow(effectiveFlow)
|
|
2203
|
-
.triplesQuery(undefined, { t: "i", i:
|
|
2352
|
+
.triplesQuery(undefined, { t: "i", i: TG_CONTAINS }, quotedTriple, 10, collection);
|
|
2204
2353
|
return triples.map((t) => getTermValue$1(t.s));
|
|
2205
2354
|
}
|
|
2206
2355
|
catch {
|
|
@@ -2208,20 +2357,21 @@ const useProvenance = (options = {}) => {
|
|
|
2208
2357
|
}
|
|
2209
2358
|
}, [socket, effectiveFlow, collection, isConnected]);
|
|
2210
2359
|
/**
|
|
2211
|
-
* Trace provenance for an edge
|
|
2360
|
+
* Trace provenance for an edge — find containing subgraph, then follow
|
|
2361
|
+
* wasDerivedFrom chain: subgraph → chunk → page → document
|
|
2212
2362
|
*/
|
|
2213
2363
|
const traceEdgeProvenance = react.useCallback(async (s, p, o) => {
|
|
2214
2364
|
setIsTracing(true);
|
|
2215
2365
|
try {
|
|
2216
|
-
// Find
|
|
2217
|
-
const
|
|
2218
|
-
// For each reifying statement, trace its provenance chain
|
|
2366
|
+
// Find subgraphs containing this edge
|
|
2367
|
+
const subgraphUris = await queryContainingSubgraphs(s, p, o);
|
|
2219
2368
|
const chains = [];
|
|
2220
|
-
for (const
|
|
2221
|
-
//
|
|
2222
|
-
const
|
|
2223
|
-
if (
|
|
2224
|
-
|
|
2369
|
+
for (const subgraphUri of subgraphUris) {
|
|
2370
|
+
// Follow wasDerivedFrom from the subgraph to the chunk
|
|
2371
|
+
const chunkUri = await queryDerivedFrom(subgraphUri);
|
|
2372
|
+
if (chunkUri) {
|
|
2373
|
+
// Trace the full chain from chunk upward
|
|
2374
|
+
const chain = await traceChain(chunkUri);
|
|
2225
2375
|
chains.push(chain);
|
|
2226
2376
|
}
|
|
2227
2377
|
}
|
|
@@ -2230,7 +2380,7 @@ const useProvenance = (options = {}) => {
|
|
|
2230
2380
|
finally {
|
|
2231
2381
|
setIsTracing(false);
|
|
2232
2382
|
}
|
|
2233
|
-
}, [
|
|
2383
|
+
}, [queryContainingSubgraphs, queryDerivedFrom, traceChain]);
|
|
2234
2384
|
/**
|
|
2235
2385
|
* Clear the label cache
|
|
2236
2386
|
*/
|
|
@@ -2252,24 +2402,26 @@ const useProvenance = (options = {}) => {
|
|
|
2252
2402
|
*
|
|
2253
2403
|
* Processing strategy:
|
|
2254
2404
|
* - Events are processed immediately as they arrive
|
|
2255
|
-
* -
|
|
2256
|
-
*
|
|
2257
|
-
* - Edge
|
|
2258
|
-
* -
|
|
2405
|
+
* - Explain triples are embedded directly in events (no store lookups)
|
|
2406
|
+
* - Edge labels are resolved via triple store queries
|
|
2407
|
+
* - Edge label resolution + provenance runs in parallel
|
|
2408
|
+
* - onUpdate callback fires on every session state change, allowing
|
|
2409
|
+
* callers to sync to external stores (e.g. Zustand)
|
|
2259
2410
|
*/
|
|
2260
2411
|
/**
|
|
2261
2412
|
* Hook for managing explainability during inference
|
|
2262
2413
|
*/
|
|
2263
2414
|
const useExplainability = (options = {}) => {
|
|
2264
2415
|
const { flow, collection = "default", traceProvenance = true, } = options;
|
|
2265
|
-
const socket = reactProvider.useSocket();
|
|
2266
|
-
const connectionState = reactProvider.useConnectionState();
|
|
2267
2416
|
const sessionFlowId = useSessionStore((state) => state.flowId);
|
|
2268
2417
|
const effectiveFlow = flow ?? sessionFlowId;
|
|
2269
2418
|
const { traceEdgeProvenance, resolveLabel } = useProvenance({
|
|
2270
2419
|
flow: effectiveFlow,
|
|
2271
2420
|
collection,
|
|
2272
2421
|
});
|
|
2422
|
+
// Keep onUpdate in a ref so it doesn't break memoisation
|
|
2423
|
+
const onUpdateRef = react.useRef(options.onUpdate);
|
|
2424
|
+
onUpdateRef.current = options.onUpdate;
|
|
2273
2425
|
const [events, setEvents] = react.useState([]);
|
|
2274
2426
|
const [session, setSession] = react.useState({});
|
|
2275
2427
|
const [isUnpacking, setIsUnpacking] = react.useState(false);
|
|
@@ -2280,98 +2432,19 @@ const useExplainability = (options = {}) => {
|
|
|
2280
2432
|
const unpackQueueRef = react.useRef([]);
|
|
2281
2433
|
const isProcessingRef = react.useRef(false);
|
|
2282
2434
|
/**
|
|
2283
|
-
*
|
|
2284
|
-
*/
|
|
2285
|
-
const isConnected = react.useCallback(() => {
|
|
2286
|
-
return (connectionState?.status === "authenticated" ||
|
|
2287
|
-
connectionState?.status === "unauthenticated");
|
|
2288
|
-
}, [connectionState]);
|
|
2289
|
-
/**
|
|
2290
|
-
* Single triple query (no retry)
|
|
2291
|
-
*/
|
|
2292
|
-
const fetchTriples = react.useCallback(async (explainId, explainGraph) => {
|
|
2293
|
-
return socket
|
|
2294
|
-
.flow(effectiveFlow)
|
|
2295
|
-
.triplesQuery({ t: "i", i: explainId }, undefined, undefined, 100, collection, explainGraph);
|
|
2296
|
-
}, [socket, effectiveFlow, collection]);
|
|
2297
|
-
/**
|
|
2298
|
-
* Stability-based retry for main event nodes.
|
|
2299
|
-
* Retries until: count > 0 AND count matches previous fetch.
|
|
2300
|
-
* Used for question, exploration, focus, synthesis nodes where the
|
|
2301
|
-
* backend may write multiple triples incrementally.
|
|
2302
|
-
*/
|
|
2303
|
-
const queryWithStabilityRetry = react.useCallback(async (explainId, explainGraph, timeoutMs = 5000) => {
|
|
2304
|
-
if (!isConnected())
|
|
2305
|
-
return [];
|
|
2306
|
-
const retryDelay = 500;
|
|
2307
|
-
const maxAttempts = Math.ceil(timeoutMs / retryDelay) + 1;
|
|
2308
|
-
let prevCount = -1;
|
|
2309
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2310
|
-
try {
|
|
2311
|
-
const triples = await fetchTriples(explainId, explainGraph);
|
|
2312
|
-
const count = triples.length;
|
|
2313
|
-
if (count > 0 && count === prevCount) {
|
|
2314
|
-
return triples;
|
|
2315
|
-
}
|
|
2316
|
-
prevCount = count;
|
|
2317
|
-
if (attempt < maxAttempts - 1) {
|
|
2318
|
-
await new Promise((r) => setTimeout(r, retryDelay));
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
catch (err) {
|
|
2322
|
-
console.error("[explain] triple query failed:", explainId, err);
|
|
2323
|
-
return [];
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
// Return last fetch if we got anything
|
|
2327
|
-
if (prevCount > 0) {
|
|
2328
|
-
try {
|
|
2329
|
-
return await fetchTriples(explainId, explainGraph);
|
|
2330
|
-
}
|
|
2331
|
-
catch {
|
|
2332
|
-
return [];
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
return [];
|
|
2336
|
-
}, [fetchTriples, isConnected]);
|
|
2337
|
-
/**
|
|
2338
|
-
* Simple retry-until-non-empty for sub-objects (edge selections).
|
|
2339
|
-
* These are small atomic writes — either fully there or not yet.
|
|
2435
|
+
* Resolve a single edge: extract from embedded triples, resolve labels
|
|
2340
2436
|
*/
|
|
2341
|
-
const
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
const
|
|
2345
|
-
const maxAttempts = Math.ceil(timeoutMs / retryDelay) + 1;
|
|
2346
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2347
|
-
try {
|
|
2348
|
-
const triples = await fetchTriples(explainId, explainGraph);
|
|
2349
|
-
if (triples.length > 0)
|
|
2350
|
-
return triples;
|
|
2351
|
-
if (attempt < maxAttempts - 1) {
|
|
2352
|
-
await new Promise((r) => setTimeout(r, retryDelay));
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
catch (err) {
|
|
2356
|
-
console.error("[explain] triple query failed:", explainId, err);
|
|
2357
|
-
return [];
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
return [];
|
|
2361
|
-
}, [fetchTriples, isConnected]);
|
|
2362
|
-
/**
|
|
2363
|
-
* Resolve a single edge: fetch triples, labels, and provenance in parallel
|
|
2364
|
-
*/
|
|
2365
|
-
const resolveEdge = react.useCallback(async (edgeSelUri, explainGraph) => {
|
|
2366
|
-
const triples = await queryWithSimpleRetry(edgeSelUri, explainGraph);
|
|
2367
|
-
const { edge, reasoning } = parseEdgeSelectionTriples(triples);
|
|
2437
|
+
const resolveEdge = react.useCallback(async (edgeSelUri, allTriples) => {
|
|
2438
|
+
// Filter embedded triples for this edge selection URI
|
|
2439
|
+
const edgeTriples = allTriples.filter((t) => getTermValue$1(t.s) === edgeSelUri);
|
|
2440
|
+
const { edge, reasoning } = parseEdgeSelectionTriples(edgeTriples);
|
|
2368
2441
|
if (!edge)
|
|
2369
2442
|
return null;
|
|
2370
2443
|
const selectedEdge = {
|
|
2371
2444
|
edge,
|
|
2372
2445
|
reasoning: reasoning || undefined,
|
|
2373
2446
|
};
|
|
2374
|
-
// Kick off
|
|
2447
|
+
// Kick off label resolution and provenance in parallel
|
|
2375
2448
|
const [labels, provenanceChains] = await Promise.all([
|
|
2376
2449
|
// Labels — all 3 in parallel
|
|
2377
2450
|
Promise.all([
|
|
@@ -2389,13 +2462,13 @@ const useExplainability = (options = {}) => {
|
|
|
2389
2462
|
selectedEdge.sources = provenanceChains.map((c) => c.chain).flat();
|
|
2390
2463
|
}
|
|
2391
2464
|
return selectedEdge;
|
|
2392
|
-
}, [
|
|
2465
|
+
}, [resolveLabel, traceProvenance, traceEdgeProvenance]);
|
|
2393
2466
|
/**
|
|
2394
2467
|
* Unpack a focus event — resolve ALL edges in parallel
|
|
2395
2468
|
*/
|
|
2396
|
-
const unpackFocusEvent = react.useCallback(async (focusEvent) => {
|
|
2469
|
+
const unpackFocusEvent = react.useCallback(async (focusEvent, allTriples) => {
|
|
2397
2470
|
// Fire off all edge resolutions concurrently
|
|
2398
|
-
const edgePromises = focusEvent.edgeSelectionUris.map((uri) => resolveEdge(uri,
|
|
2471
|
+
const edgePromises = focusEvent.edgeSelectionUris.map((uri) => resolveEdge(uri, allTriples));
|
|
2399
2472
|
const results = await Promise.all(edgePromises);
|
|
2400
2473
|
const selectedEdges = results.filter((e) => e !== null);
|
|
2401
2474
|
return {
|
|
@@ -2403,50 +2476,88 @@ const useExplainability = (options = {}) => {
|
|
|
2403
2476
|
selectedEdges,
|
|
2404
2477
|
};
|
|
2405
2478
|
}, [resolveEdge]);
|
|
2406
|
-
/** Helper to update
|
|
2479
|
+
/** Helper to update state, ref, and notify caller together */
|
|
2407
2480
|
const updateSession = react.useCallback((updater) => {
|
|
2408
2481
|
sessionRef.current = updater(sessionRef.current);
|
|
2409
2482
|
setSession(updater);
|
|
2483
|
+
onUpdateRef.current?.(sessionRef.current);
|
|
2410
2484
|
}, []);
|
|
2485
|
+
const AGENT_STEP_TYPES = new Set([
|
|
2486
|
+
"agent-question", "decomposition", "analysis", "reflection", "conclusion",
|
|
2487
|
+
"grounding",
|
|
2488
|
+
]);
|
|
2489
|
+
/**
|
|
2490
|
+
* Resolve labels for inline edges (edges already extracted, just need labels)
|
|
2491
|
+
*/
|
|
2492
|
+
const resolveEdgeLabels = react.useCallback(async (focusEvent) => {
|
|
2493
|
+
if (!focusEvent.selectedEdges || focusEvent.selectedEdges.length === 0) {
|
|
2494
|
+
return focusEvent;
|
|
2495
|
+
}
|
|
2496
|
+
const resolved = await Promise.all(focusEvent.selectedEdges.map(async (se) => {
|
|
2497
|
+
const [sLabel, pLabel, oLabel] = await Promise.all([
|
|
2498
|
+
resolveLabel(se.edge.s),
|
|
2499
|
+
resolveLabel(se.edge.p),
|
|
2500
|
+
resolveLabel(se.edge.o),
|
|
2501
|
+
]);
|
|
2502
|
+
let sources;
|
|
2503
|
+
if (traceProvenance) {
|
|
2504
|
+
const chains = await traceEdgeProvenance(se.edge.s, se.edge.p, se.edge.o);
|
|
2505
|
+
if (chains.length > 0) {
|
|
2506
|
+
sources = chains.map((c) => c.chain).flat();
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
return {
|
|
2510
|
+
...se,
|
|
2511
|
+
labels: { s: sLabel, p: pLabel, o: oLabel },
|
|
2512
|
+
sources,
|
|
2513
|
+
};
|
|
2514
|
+
}));
|
|
2515
|
+
return { ...focusEvent, selectedEdges: resolved };
|
|
2516
|
+
}, [resolveLabel, traceProvenance, traceEdgeProvenance]);
|
|
2411
2517
|
/**
|
|
2412
2518
|
* Process a single explain event
|
|
2413
2519
|
*/
|
|
2414
2520
|
const processEvent = react.useCallback(async (event) => {
|
|
2415
|
-
|
|
2416
|
-
const triples = await queryWithStabilityRetry(event.explainId, event.explainGraph);
|
|
2417
|
-
// Parse into structured data
|
|
2521
|
+
const triples = event.explainTriples ?? [];
|
|
2418
2522
|
const parsed = parseExplainTriples(event.explainId, event.explainGraph, triples);
|
|
2419
2523
|
if (!parsed)
|
|
2420
2524
|
return;
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
const next = { ...prev };
|
|
2424
|
-
switch (parsed.type) {
|
|
2425
|
-
case "question":
|
|
2426
|
-
next.question = parsed;
|
|
2427
|
-
break;
|
|
2428
|
-
case "exploration":
|
|
2429
|
-
next.exploration = parsed;
|
|
2430
|
-
break;
|
|
2431
|
-
case "focus":
|
|
2432
|
-
// Will be updated again after unpacking
|
|
2433
|
-
next.focus = parsed;
|
|
2434
|
-
break;
|
|
2435
|
-
case "synthesis":
|
|
2436
|
-
next.synthesis = parsed;
|
|
2437
|
-
break;
|
|
2438
|
-
}
|
|
2439
|
-
return next;
|
|
2440
|
-
});
|
|
2441
|
-
// For focus events, unpack edges in parallel
|
|
2442
|
-
if (parsed.type === "focus") {
|
|
2443
|
-
const unpackedFocus = await unpackFocusEvent(parsed);
|
|
2525
|
+
if (AGENT_STEP_TYPES.has(parsed.type)) {
|
|
2526
|
+
// Agent step — append to timeline
|
|
2444
2527
|
updateSession((prev) => ({
|
|
2445
2528
|
...prev,
|
|
2446
|
-
|
|
2529
|
+
agentSteps: [...(prev.agentSteps || []), parsed],
|
|
2447
2530
|
}));
|
|
2448
2531
|
}
|
|
2449
|
-
|
|
2532
|
+
// Graph-RAG fields — populate regardless (agent produces these too)
|
|
2533
|
+
switch (parsed.type) {
|
|
2534
|
+
case "question":
|
|
2535
|
+
updateSession((prev) => ({ ...prev, question: parsed }));
|
|
2536
|
+
break;
|
|
2537
|
+
case "exploration":
|
|
2538
|
+
updateSession((prev) => ({ ...prev, exploration: parsed }));
|
|
2539
|
+
break;
|
|
2540
|
+
case "focus": {
|
|
2541
|
+
updateSession((prev) => ({ ...prev, focus: parsed }));
|
|
2542
|
+
// Resolve labels for inline edges, or unpack old-format edges
|
|
2543
|
+
const focusEvent = parsed;
|
|
2544
|
+
if (focusEvent.selectedEdges && focusEvent.selectedEdges.length > 0) {
|
|
2545
|
+
// New format: edges already extracted, resolve labels
|
|
2546
|
+
const resolved = await resolveEdgeLabels(focusEvent);
|
|
2547
|
+
updateSession((prev) => ({ ...prev, focus: resolved }));
|
|
2548
|
+
}
|
|
2549
|
+
else if (focusEvent.edgeSelectionUris.length > 0) {
|
|
2550
|
+
// Old format: unpack edge selection URIs
|
|
2551
|
+
const unpacked = await unpackFocusEvent(focusEvent, triples);
|
|
2552
|
+
updateSession((prev) => ({ ...prev, focus: unpacked }));
|
|
2553
|
+
}
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2556
|
+
case "synthesis":
|
|
2557
|
+
updateSession((prev) => ({ ...prev, synthesis: parsed }));
|
|
2558
|
+
break;
|
|
2559
|
+
}
|
|
2560
|
+
}, [unpackFocusEvent, resolveEdgeLabels, updateSession]);
|
|
2450
2561
|
/**
|
|
2451
2562
|
* Process the unpack queue
|
|
2452
2563
|
*/
|
|
@@ -2529,10 +2640,18 @@ const useChatSession = ({ flow } = {}) => {
|
|
|
2529
2640
|
const { settings } = useSettings();
|
|
2530
2641
|
// Explainability store for persisting sessions
|
|
2531
2642
|
const addExplainSession = useExplainabilityStore((state) => state.addSession);
|
|
2532
|
-
//
|
|
2643
|
+
// Track the current explain session ID so onUpdate can target the right store key
|
|
2644
|
+
const explainSessionIdRef = react.useRef(undefined);
|
|
2645
|
+
// Explainability hook — onUpdate syncs every state change to the Zustand store
|
|
2533
2646
|
const explainability = useExplainability({
|
|
2534
2647
|
flow: effectiveFlow,
|
|
2535
2648
|
collection: settings.collection,
|
|
2649
|
+
onUpdate: (session) => {
|
|
2650
|
+
const id = explainSessionIdRef.current;
|
|
2651
|
+
if (id) {
|
|
2652
|
+
addExplainSession(id, session);
|
|
2653
|
+
}
|
|
2654
|
+
},
|
|
2536
2655
|
});
|
|
2537
2656
|
const explainabilityRef = react.useRef(explainability);
|
|
2538
2657
|
explainabilityRef.current = explainability;
|
|
@@ -2551,13 +2670,9 @@ const useChatSession = ({ flow } = {}) => {
|
|
|
2551
2670
|
addActivity(ragActivity);
|
|
2552
2671
|
let accumulated = "";
|
|
2553
2672
|
let messageAdded = false;
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
// Reset explainability state for new query
|
|
2558
|
-
if (explainabilityEnabled) {
|
|
2559
|
-
explainabilityRef.current.reset();
|
|
2560
|
-
}
|
|
2673
|
+
const sessionId = generateSessionId();
|
|
2674
|
+
explainabilityRef.current.reset();
|
|
2675
|
+
explainSessionIdRef.current = sessionId;
|
|
2561
2676
|
try {
|
|
2562
2677
|
// Execute Graph RAG with streaming and entity discovery
|
|
2563
2678
|
const result = await inference.graphRag({
|
|
@@ -2573,41 +2688,20 @@ const useChatSession = ({ flow } = {}) => {
|
|
|
2573
2688
|
onChunk: (chunk, complete) => {
|
|
2574
2689
|
accumulated += chunk;
|
|
2575
2690
|
if (!messageAdded) {
|
|
2576
|
-
// Add empty message on first chunk (with session ID if enabled)
|
|
2577
2691
|
addMessage("ai", accumulated, undefined, sessionId);
|
|
2578
2692
|
messageAdded = true;
|
|
2579
2693
|
}
|
|
2580
2694
|
else {
|
|
2581
|
-
// Update existing message with accumulated text
|
|
2582
2695
|
updateLastMessage(accumulated);
|
|
2583
2696
|
}
|
|
2584
2697
|
},
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
explainabilityRef.current.addEvent(event);
|
|
2590
|
-
},
|
|
2591
|
-
}),
|
|
2698
|
+
onExplain: (event) => {
|
|
2699
|
+
console.log("[explain] event received:", event.explainId);
|
|
2700
|
+
explainabilityRef.current.addEvent(event);
|
|
2701
|
+
},
|
|
2592
2702
|
},
|
|
2593
2703
|
});
|
|
2594
2704
|
removeActivity(ragActivity);
|
|
2595
|
-
// Store explainability session progressively — events are already being
|
|
2596
|
-
// processed as they arrive. Wait for processing to finish, then snapshot.
|
|
2597
|
-
if (explainabilityEnabled && sessionId) {
|
|
2598
|
-
const waitAndStore = async () => {
|
|
2599
|
-
// Poll until processing completes (events are processed as they arrive)
|
|
2600
|
-
const maxWait = 30000;
|
|
2601
|
-
let elapsed = 0;
|
|
2602
|
-
while (explainabilityRef.current.isProcessingRef.current && elapsed < maxWait) {
|
|
2603
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
2604
|
-
elapsed += 500;
|
|
2605
|
-
}
|
|
2606
|
-
const sess = explainabilityRef.current.sessionRef.current;
|
|
2607
|
-
addExplainSession(sessionId, sess);
|
|
2608
|
-
};
|
|
2609
|
-
waitAndStore();
|
|
2610
|
-
}
|
|
2611
2705
|
// Start embeddings activity
|
|
2612
2706
|
addActivity(embActivity);
|
|
2613
2707
|
// Get labels for each entity
|
|
@@ -2695,9 +2789,13 @@ const useChatSession = ({ flow } = {}) => {
|
|
|
2695
2789
|
let observationMessageAdded = false;
|
|
2696
2790
|
let answerAccumulated = "";
|
|
2697
2791
|
let answerMessageAdded = false;
|
|
2792
|
+
const sessionId = generateSessionId();
|
|
2793
|
+
explainabilityRef.current.reset();
|
|
2794
|
+
explainSessionIdRef.current = sessionId;
|
|
2698
2795
|
try {
|
|
2699
2796
|
const response = await inference.agent({
|
|
2700
2797
|
input,
|
|
2798
|
+
collection: settings.collection,
|
|
2701
2799
|
callbacks: {
|
|
2702
2800
|
onThink: (thought, complete) => {
|
|
2703
2801
|
thinkingAccumulated += thought;
|
|
@@ -2730,13 +2828,17 @@ const useChatSession = ({ flow } = {}) => {
|
|
|
2730
2828
|
onAnswer: (answer, complete) => {
|
|
2731
2829
|
answerAccumulated += answer;
|
|
2732
2830
|
if (!answerMessageAdded) {
|
|
2733
|
-
addMessage("ai", answerAccumulated, "answer");
|
|
2831
|
+
addMessage("ai", answerAccumulated, "answer", sessionId);
|
|
2734
2832
|
answerMessageAdded = true;
|
|
2735
2833
|
}
|
|
2736
2834
|
else {
|
|
2737
2835
|
updateLastMessage(answerAccumulated);
|
|
2738
2836
|
}
|
|
2739
2837
|
},
|
|
2838
|
+
onExplain: (event) => {
|
|
2839
|
+
console.log("[explain] agent event received:", event.explainId);
|
|
2840
|
+
explainabilityRef.current.addEvent(event);
|
|
2841
|
+
},
|
|
2740
2842
|
},
|
|
2741
2843
|
});
|
|
2742
2844
|
removeActivity(activity);
|
|
@@ -6093,6 +6195,7 @@ exports.extractQuotedTriple = extractQuotedTriple;
|
|
|
6093
6195
|
exports.fileToBase64 = fileToBase64;
|
|
6094
6196
|
exports.generateFlowBlueprintId = generateFlowBlueprintId;
|
|
6095
6197
|
exports.getEventType = getEventType;
|
|
6198
|
+
exports.getEventTypeFromTriples = getEventTypeFromTriples;
|
|
6096
6199
|
exports.getExplainTermValue = getTermValue$1;
|
|
6097
6200
|
exports.getTermValue = getTermValue$3;
|
|
6098
6201
|
exports.getTriples = getTriples;
|