@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.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useSocket, useConnectionState } from '@trustgraph/react-provider';
2
2
  export { ConnectionStateContext, SocketContext, SocketProvider } from '@trustgraph/react-provider';
3
- import { TG_EDGE, TG_REASONING, TG_EDGE_COUNT, TG_SELECTED_EDGE, TG_QUERY, PROV_STARTED_AT_TIME, TG_CONTENT, RDFS_LABEL as RDFS_LABEL$2, PROV_WAS_DERIVED_FROM, TG_REIFIES } from '@trustgraph/client';
3
+ import { RDF_TYPE as RDF_TYPE$1, TG_EDGE, TG_REASONING, TG_EDGE_COUNT, TG_SELECTED_EDGE, TG_QUERY, PROV_STARTED_AT_TIME, TG_CONTENT, TG, PROV_WAS_DERIVED_FROM, RDFS_LABEL as RDFS_LABEL$2 } from '@trustgraph/client';
4
4
  export { PROV, PROV_ACTIVITY, PROV_ENTITY, PROV_STARTED_AT_TIME, PROV_WAS_DERIVED_FROM, PROV_WAS_GENERATED_BY, RDF, RDFS, RDF_TYPE, SCHEMA, SCHEMA_AUTHOR, SCHEMA_DESCRIPTION, SCHEMA_KEYWORDS, SCHEMA_NAME, SKOS, SKOS_DEFINITION, TG, TG_CONTENT, TG_DOCUMENT, TG_EDGE, TG_EDGE_COUNT, TG_QUERY, TG_REASONING, TG_REIFIES, TG_SELECTED_EDGE } from '@trustgraph/client';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
  import { createContext, useContext, useEffect, useState, useRef, useCallback, useMemo } from 'react';
@@ -1922,9 +1922,28 @@ const useExplainabilityStore = 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 = TG + "action";
1927
+ const TG_ARGUMENTS = TG + "arguments";
1928
+ const TG_SUBAGENT_GOAL = TG + "subagentGoal";
1929
+ const TG_CONCEPT = TG + "concept";
1930
+ const TG_ENTITY = TG + "entity";
1931
+ // RDF type URIs
1932
+ const TG_AGENT_QUESTION = TG + "AgentQuestion";
1933
+ const TG_ANALYSIS = TG + "Analysis";
1934
+ const TG_TOOL_USE = TG + "ToolUse";
1935
+ const TG_OBSERVATION = TG + "Observation";
1936
+ const TG_THOUGHT_TYPE = TG + "Thought";
1937
+ const TG_REFLECTION_TYPE = TG + "Reflection";
1938
+ const TG_CONCLUSION = TG + "Conclusion";
1939
+ const TG_FINDING = TG + "Finding";
1940
+ const TG_DECOMPOSITION = TG + "Decomposition";
1941
+ const TG_GROUNDING = TG + "Grounding";
1942
+ const TG_EXPLORATION_TYPE = TG + "Exploration";
1943
+ const TG_FOCUS_TYPE = 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) === RDF_TYPE$1) {
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 === RDFS_LABEL$2 && o)
2030
+ label = o;
2031
+ if (p === PROV_WAS_DERIVED_FROM && o)
2032
+ derivedFrom.push(o);
2033
+ }
2034
+ return { label, derivedFrom };
2035
+ }
1972
2036
  /**
1973
- * Parse triples for a question event
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) === 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
- type: "question",
1978
- explainId,
1979
- explainGraph,
1980
- };
1981
- for (const triple of triples) {
1982
- const p = getTermValue$1(triple.p);
1983
- const o = getTermValue$1(triple.o);
1984
- if (p === 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 === TG_QUERY)
1985
2059
  event.query = o;
1986
- }
1987
- else if (p === PROV_STARTED_AT_TIME) {
2060
+ else if (p === 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
- type: "exploration",
1999
- explainId,
2000
- explainGraph,
2001
- };
2002
- for (const triple of triples) {
2003
- const p = getTermValue$1(triple.p);
2004
- const o = getTermValue$1(triple.o);
2005
- if (p === 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 === 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
- for (const triple of triples) {
2022
- const p = getTermValue$1(triple.p);
2023
- const o = getTermValue$1(triple.o);
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 === 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
- type: "synthesis",
2036
- explainId,
2037
- explainGraph,
2038
- };
2039
- for (const triple of triples) {
2040
- const p = getTermValue$1(triple.p);
2041
- const o = getTermValue$1(triple.o);
2042
- if (p === 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 === 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 === TG_EDGE) {
2117
+ if (p === TG_EDGE)
2057
2118
  edge = extractQuotedTriple(triple.o);
2058
- }
2059
- else if (p === TG_REASONING) {
2119
+ else if (p === 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 === TG_QUERY)
2144
+ event.query = o;
2145
+ if (p === 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 based on event type
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 prov:wasDerivedFrom relationships from any entity to its source documents
2233
+ * Follows tg:contains to find subgraphs, then prov:wasDerivedFrom
2234
+ * to trace chunk → page → document chains
2087
2235
  */
2236
+ const TG_CONTAINS = 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
- * Query for statements that reify an edge via tg:reifies
2333
+ * Find subgraphs that contain an edge via tg:contains with a quoted triple
2185
2334
  */
2186
- const queryReifyingStatements = useCallback(async (s, p, o) => {
2335
+ const queryContainingSubgraphs = 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: TG_REIFIES }, quotedTriple, 10, collection);
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 - finds reifying statements and traces each
2360
+ * Trace provenance for an edge find containing subgraph, then follow
2361
+ * wasDerivedFrom chain: subgraph → chunk → page → document
2212
2362
  */
2213
2363
  const traceEdgeProvenance = useCallback(async (s, p, o) => {
2214
2364
  setIsTracing(true);
2215
2365
  try {
2216
- // Find statements that reify this edge
2217
- const stmtUris = await queryReifyingStatements(s, p, o);
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 stmtUri of stmtUris) {
2221
- // Get the wasDerivedFrom source for this statement
2222
- const sourceUri = await queryDerivedFrom(stmtUri);
2223
- if (sourceUri) {
2224
- const chain = await traceChain(sourceUri);
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
- }, [queryReifyingStatements, queryDerivedFrom, traceChain]);
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
- * - Main event nodes (question, exploration, focus, synthesis) use
2256
- * stability-based retry: fetch until count > 0 AND stable
2257
- * - Edge sub-objects use simple retry-until-non-empty (fast)
2258
- * - All edge fetching + label resolution + provenance runs in parallel
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 = useSocket();
2266
- const connectionState = 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 = useRef(options.onUpdate);
2424
+ onUpdateRef.current = options.onUpdate;
2273
2425
  const [events, setEvents] = useState([]);
2274
2426
  const [session, setSession] = useState({});
2275
2427
  const [isUnpacking, setIsUnpacking] = useState(false);
@@ -2280,98 +2432,19 @@ const useExplainability = (options = {}) => {
2280
2432
  const unpackQueueRef = useRef([]);
2281
2433
  const isProcessingRef = useRef(false);
2282
2434
  /**
2283
- * Check if connected
2284
- */
2285
- const isConnected = useCallback(() => {
2286
- return (connectionState?.status === "authenticated" ||
2287
- connectionState?.status === "unauthenticated");
2288
- }, [connectionState]);
2289
- /**
2290
- * Single triple query (no retry)
2291
- */
2292
- const fetchTriples = 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 = 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 queryWithSimpleRetry = useCallback(async (explainId, explainGraph, timeoutMs = 5000) => {
2342
- if (!isConnected())
2343
- return [];
2344
- const retryDelay = 300;
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 = useCallback(async (edgeSelUri, explainGraph) => {
2366
- const triples = await queryWithSimpleRetry(edgeSelUri, explainGraph);
2367
- const { edge, reasoning } = parseEdgeSelectionTriples(triples);
2437
+ const resolveEdge = 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 labels and provenance in parallel
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
- }, [queryWithSimpleRetry, resolveLabel, traceProvenance, traceEdgeProvenance]);
2465
+ }, [resolveLabel, traceProvenance, traceEdgeProvenance]);
2393
2466
  /**
2394
2467
  * Unpack a focus event — resolve ALL edges in parallel
2395
2468
  */
2396
- const unpackFocusEvent = useCallback(async (focusEvent) => {
2469
+ const unpackFocusEvent = useCallback(async (focusEvent, allTriples) => {
2397
2470
  // Fire off all edge resolutions concurrently
2398
- const edgePromises = focusEvent.edgeSelectionUris.map((uri) => resolveEdge(uri, focusEvent.explainGraph));
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 both state and ref together */
2479
+ /** Helper to update state, ref, and notify caller together */
2407
2480
  const updateSession = 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 = 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 = useCallback(async (event) => {
2415
- // Query triples for main event node (stability retry)
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
- // Update session based on event type
2422
- updateSession((prev) => {
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
- focus: unpackedFocus,
2529
+ agentSteps: [...(prev.agentSteps || []), parsed],
2447
2530
  }));
2448
2531
  }
2449
- }, [queryWithStabilityRetry, unpackFocusEvent, updateSession]);
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
- // Explainability hook for processing events (processes each event immediately)
2643
+ // Track the current explain session ID so onUpdate can target the right store key
2644
+ const explainSessionIdRef = 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 = 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
- // Check if explainability is enabled
2555
- const explainabilityEnabled = settings.featureSwitches.explainability;
2556
- const sessionId = explainabilityEnabled ? generateSessionId() : undefined;
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
- // Wire up explainability callback if feature is enabled
2586
- ...(explainabilityEnabled && {
2587
- onExplain: (event) => {
2588
- console.log("[explain] event received:", event.explainId);
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);
@@ -5972,5 +6074,5 @@ const useDocumentsMetadata = (documentIds = []) => {
5972
6074
  };
5973
6075
  };
5974
6076
 
5975
- export { DEFAULT_SETTINGS, NotificationProvider, RDFS_LABEL, SETTINGS_STORAGE_KEY, createDocId, extractQuotedTriple, fileToBase64, generateFlowBlueprintId, getEventType, getTermValue$1 as getExplainTermValue, getTermValue$3 as getTermValue, getTriples, parseEdgeSelectionTriples, parseExplainTriples, parseExplorationTriples, parseFocusTriples, parseQuestionTriples, parseSynthesisTriples, prepareMetadata, textToBase64, useActivity, useAgentTools, useChat, useChatSession, useChunkedDownload, useChunkedUpload, useCollections, useConversation, useDocumentEmbeddingsQuery, useDocumentMetadata, useDocumentsMetadata, useEmbeddings, useEntityDetail, useExplainability, useExplainabilityStore, useFlowBlueprints, useFlowParameters, useFlows, useGraphEmbeddings, useGraphSubgraph, useInference, useKnowledgeCores, useLLMModels, useLibrary, useLoadStateStore, useMcpTools, useNlpQuery, useNodeDetails, useNotification, useOntologies, useParameterValidation, useProcessing, useProgressStateStore, usePrompts, useProvenance, useRowEmbeddingsQuery, useRowsQuery, useSchemas, useSearchStateStore, useSessionStore, useSettings, useStructuredQuery, useTokenCosts, useTriples, useVectorSearch, useWorkbenchStateStore, vectorSearch };
6077
+ export { DEFAULT_SETTINGS, NotificationProvider, RDFS_LABEL, SETTINGS_STORAGE_KEY, createDocId, extractQuotedTriple, fileToBase64, generateFlowBlueprintId, getEventType, getEventTypeFromTriples, getTermValue$1 as getExplainTermValue, getTermValue$3 as getTermValue, getTriples, parseEdgeSelectionTriples, parseExplainTriples, parseExplorationTriples, parseFocusTriples, parseQuestionTriples, parseSynthesisTriples, prepareMetadata, textToBase64, useActivity, useAgentTools, useChat, useChatSession, useChunkedDownload, useChunkedUpload, useCollections, useConversation, useDocumentEmbeddingsQuery, useDocumentMetadata, useDocumentsMetadata, useEmbeddings, useEntityDetail, useExplainability, useExplainabilityStore, useFlowBlueprints, useFlowParameters, useFlows, useGraphEmbeddings, useGraphSubgraph, useInference, useKnowledgeCores, useLLMModels, useLibrary, useLoadStateStore, useMcpTools, useNlpQuery, useNodeDetails, useNotification, useOntologies, useParameterValidation, useProcessing, useProgressStateStore, usePrompts, useProvenance, useRowEmbeddingsQuery, useRowsQuery, useSchemas, useSearchStateStore, useSessionStore, useSettings, useStructuredQuery, useTokenCosts, useTriples, useVectorSearch, useWorkbenchStateStore, vectorSearch };
5976
6078
  //# sourceMappingURL=index.esm.js.map