From bbaf2578408849c13f93cc08ac889285161b400d Mon Sep 17 00:00:00 2001 From: Cyber MacGeddon Date: Wed, 8 Apr 2026 09:06:22 +0100 Subject: [PATCH 1/2] Add agent explainability support and use embedded explain triples - Remove triple store lookups for explain events; use embedded explainTriples from events directly (only edge label resolution still queries the store) - Detect event types from RDF types in embedded triples instead of URI pattern matching - Add agent event parsers (AgentQuestion, Decomposition, Analysis, Reflection, Conclusion) alongside existing graph-rag parsers - Wire explainability into agent chat handler with onUpdate callback to sync session state to Zustand store progressively - Add AgentExplainTimeline component showing agent reasoning steps - Remove unused explainability feature switch gate --- package-lock.json | 124 +++++++++++------- package.json | 4 +- src/components/chat/ChatMessage.tsx | 9 +- src/components/chat/ExplainabilityPanel.tsx | 19 ++- .../explainability/AgentExplainTimeline.tsx | 122 +++++++++++++++++ src/components/chat/explainability/index.ts | 1 + 6 files changed, 219 insertions(+), 60 deletions(-) create mode 100644 src/components/chat/explainability/AgentExplainTimeline.tsx diff --git a/package-lock.json b/package-lock.json index 012c834..65c07ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,9 @@ "@msgpack/msgpack": "^3.1.1", "@tanstack/react-query": "^5.80.3", "@tanstack/react-table": "^8.21.3", - "@trustgraph/client": "^1.5.3", + "@trustgraph/client": "^1.7.1", "@trustgraph/react-provider": "^1.4.0", - "@trustgraph/react-state": "^1.5.4", + "@trustgraph/react-state": "^1.7.2", "@types/dagre": "^0.7.53", "compute-cosine-similarity": "^1.1.0", "dagre": "^0.8.5", @@ -58,6 +58,50 @@ "vitest": "^3.2.4" } }, + "../trustgraph-react-state": { + "name": "@trustgraph/react-state", + "version": "1.7.2", + "license": "MIT", + "dependencies": { + "@trustgraph/react-provider": "^1.4.0", + "compute-cosine-similarity": "^1.1.0", + "uuid": "^11.0.3" + }, + "devDependencies": { + "@eslint/js": "^9.37.0", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-typescript": "^11.1.6", + "@tanstack/react-query": "^5.0.0", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.0", + "@trustgraph/client": "^1.7.2", + "@trustgraph/react-provider": "^1.4.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "@vitest/ui": "^3.2.4", + "eslint": "^8.57.1", + "globals": "^16.4.0", + "happy-dom": "^20.0.10", + "jiti": "^2.6.1", + "prettier": "^3.6.2", + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0", + "rollup": "^4.9.0", + "tslib": "^2.6.2", + "typescript": "^5.3.3", + "typescript-eslint": "^8.46.0", + "uuid": "^11.0.3", + "vitest": "^3.2.4", + "zustand": "^5.0.2" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.0.0", + "react": "^18.0.0 || ^19.0.0", + "zustand": "^4.0.0 || ^5.0.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", @@ -2617,9 +2661,9 @@ } }, "node_modules/@trustgraph/client": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@trustgraph/client/-/client-1.5.3.tgz", - "integrity": "sha512-1gzsD5RakMgP8VzfqIVsrlmJsgaltNbMT6dMawJSJJ1vqujpgEARKHf/Qw3pEfpXZrRvmesRCKE2x9q6DrW7wg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@trustgraph/client/-/client-1.7.2.tgz", + "integrity": "sha512-9Ps/hfukTDvuabF/lXQtOGvs4P5/g6yv6Bq1vU3bLOFN5rMQNJZa6kYAOburV4RE8yK25qLi+WC/En2H/qjKOg==", "license": "Apache-2.0" }, "node_modules/@trustgraph/react-provider": { @@ -2633,20 +2677,8 @@ } }, "node_modules/@trustgraph/react-state": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@trustgraph/react-state/-/react-state-1.5.4.tgz", - "integrity": "sha512-2uPA+npf7RkLIRmsysDkfAmGhnM1OgKk8P9B11tNbJcqnkPlyKqEd2g03G4XjdQ8uG4dHPY0c1qpzol0TD5S0w==", - "license": "MIT", - "dependencies": { - "@trustgraph/react-provider": "^1.4.0", - "compute-cosine-similarity": "^1.1.0", - "uuid": "^11.0.3" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.0.0", - "react": "^18.0.0 || ^19.0.0", - "zustand": "^4.0.0 || ^5.0.0" - } + "resolved": "../trustgraph-react-state", + "link": true }, "node_modules/@tweenjs/tween.js": { "version": "25.0.0", @@ -3237,9 +3269,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4716,9 +4748,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -5103,9 +5135,9 @@ } }, "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -5966,9 +5998,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -6680,15 +6712,15 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -7318,9 +7350,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8892,9 +8924,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9281,9 +9313,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "optional": true, diff --git a/package.json b/package.json index f5ca4a4..98db779 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "@msgpack/msgpack": "^3.1.1", "@tanstack/react-query": "^5.80.3", "@tanstack/react-table": "^8.21.3", - "@trustgraph/client": "^1.5.3", + "@trustgraph/client": "^1.7.1", "@trustgraph/react-provider": "^1.4.0", - "@trustgraph/react-state": "^1.5.4", + "@trustgraph/react-state": "^1.7.2", "@types/dagre": "^0.7.53", "compute-cosine-similarity": "^1.1.0", "dagre": "^0.8.5", diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx index 9ab51e9..7a5cf8f 100644 --- a/src/components/chat/ChatMessage.tsx +++ b/src/components/chat/ChatMessage.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Box, Flex, Avatar, Badge, IconButton } from "@chakra-ui/react"; import { Brain, Eye, CheckCircle, ChevronDown, ChevronRight } from "lucide-react"; import Markdown from "react-markdown-it"; -import { useSettings, useExplainabilityStore } from "@trustgraph/react-state"; +import { useExplainabilityStore } from "@trustgraph/react-state"; import ExplainabilityPanel from "./ExplainabilityPanel"; // Smart truncation: first sentence if short, otherwise ~100 chars at word boundary @@ -26,19 +26,14 @@ const ChatMessage = ({ message }) => { const isUser = message.role === "human"; const messageType = message.type || "normal"; - // Settings for feature switches - const { settings } = useSettings(); - const explainabilityEnabled = settings.featureSwitches.explainability; - // Check if session exists for this message const sessionExists = useExplainabilityStore( (state) => !!message.explainSessionId && !!state.sessions[message.explainSessionId] ); - // Determine if explainability panel should be shown + // Show explainability panel if this message has a session with data const showExplainability = !isUser && - explainabilityEnabled && message.explainSessionId && sessionExists; diff --git a/src/components/chat/ExplainabilityPanel.tsx b/src/components/chat/ExplainabilityPanel.tsx index de0b8ba..6812808 100644 --- a/src/components/chat/ExplainabilityPanel.tsx +++ b/src/components/chat/ExplainabilityPanel.tsx @@ -8,6 +8,7 @@ import { ExplorationSection, FocusSection, SourceDrawer, + AgentExplainTimeline, } from "./explainability"; interface ExplainabilityPanelProps { @@ -25,7 +26,9 @@ const ExplainabilityPanel = ({ sessionId }: ExplainabilityPanelProps) => { if (!session) return null; // Check if there's any meaningful data to show + const hasAgentSteps = session.agentSteps && session.agentSteps.length > 0; const hasData = + hasAgentSteps || session.question?.query || session.exploration?.edgeCount !== undefined || (session.focus?.selectedEdges && session.focus.selectedEdges.length > 0); @@ -67,17 +70,23 @@ const ExplainabilityPanel = ({ sessionId }: ExplainabilityPanelProps) => { - How I found this answer + {hasAgentSteps ? "How the agent reasoned" : "How I found this answer"} - {session.question && } - {session.exploration && } - {session.focus && ( - + {hasAgentSteps ? ( + + ) : ( + <> + {session.question && } + {session.exploration && } + {session.focus && ( + + )} + )} diff --git a/src/components/chat/explainability/AgentExplainTimeline.tsx b/src/components/chat/explainability/AgentExplainTimeline.tsx new file mode 100644 index 0000000..bdaa8c5 --- /dev/null +++ b/src/components/chat/explainability/AgentExplainTimeline.tsx @@ -0,0 +1,122 @@ +import { Box, Text, Badge, VStack, Flex } from "@chakra-ui/react"; +import { + HelpCircle, + GitBranch, + Wrench, + MessageSquare, + CheckCircle, +} from "lucide-react"; +import type { + StructuredExplainEvent, + AgentQuestionEvent, + DecompositionEvent, + AnalysisEvent, +} from "@trustgraph/react-state"; + +const STEP_CONFIG: Record< + string, + { color: string; icon: React.ReactNode; defaultLabel: string } +> = { + "agent-question": { + color: "orange", + icon: , + defaultLabel: "Question", + }, + decomposition: { + color: "yellow", + icon: , + defaultLabel: "Decomposition", + }, + analysis: { + color: "purple", + icon: , + defaultLabel: "Analysis", + }, + reflection: { + color: "cyan", + icon: , + defaultLabel: "Observation", + }, + conclusion: { + color: "green", + icon: , + defaultLabel: "Conclusion", + }, +}; + +/** Render type-specific detail below the badge */ +const StepDetail = ({ step }: { step: StructuredExplainEvent }) => { + switch (step.type) { + case "agent-question": { + const q = (step as AgentQuestionEvent).query; + return q ? ( + "{q}" + ) : null; + } + case "decomposition": { + const goals = (step as DecompositionEvent).goals; + return goals.length > 0 ? ( + + {goals.map((g, i) => ( + - {g} + ))} + + ) : null; + } + case "analysis": { + const a = step as AnalysisEvent; + return a.action ? ( + + {a.action} + {a.arguments && ( + + {a.arguments.length > 80 + ? a.arguments.substring(0, 80) + "..." + : a.arguments} + + )} + + ) : null; + } + default: + return null; + } +}; + +interface AgentExplainTimelineProps { + steps: StructuredExplainEvent[]; +} + +const AgentExplainTimeline = ({ steps }: AgentExplainTimelineProps) => { + if (steps.length === 0) return null; + + return ( + + {steps.map((step, i) => { + const config = STEP_CONFIG[step.type] || STEP_CONFIG.reflection; + const label = + ("label" in step && step.label) || config.defaultLabel; + + return ( + + + + {config.icon} + {label} + + + + + + + ); + })} + + ); +}; + +export default AgentExplainTimeline; diff --git a/src/components/chat/explainability/index.ts b/src/components/chat/explainability/index.ts index 2ff967c..e60a5d4 100644 --- a/src/components/chat/explainability/index.ts +++ b/src/components/chat/explainability/index.ts @@ -3,3 +3,4 @@ export { default as ExplorationSection } from "./ExplorationSection"; export { default as FocusSection } from "./FocusSection"; export { default as SourceLink } from "./SourceLink"; export { default as SourceDrawer } from "./SourceDrawer"; +export { default as AgentExplainTimeline } from "./AgentExplainTimeline"; From 4d675b3f1e6f49530e97580929f1fcfeae70822b Mon Sep 17 00:00:00 2001 From: Cyber MacGeddon Date: Wed, 8 Apr 2026 15:39:52 +0100 Subject: [PATCH 2/2] Maybe fix package-lock --- package-lock.json | 60 +++++++++++------------------------------------ 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65c07ee..f1313d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,50 +58,6 @@ "vitest": "^3.2.4" } }, - "../trustgraph-react-state": { - "name": "@trustgraph/react-state", - "version": "1.7.2", - "license": "MIT", - "dependencies": { - "@trustgraph/react-provider": "^1.4.0", - "compute-cosine-similarity": "^1.1.0", - "uuid": "^11.0.3" - }, - "devDependencies": { - "@eslint/js": "^9.37.0", - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-typescript": "^11.1.6", - "@tanstack/react-query": "^5.0.0", - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.0", - "@trustgraph/client": "^1.7.2", - "@trustgraph/react-provider": "^1.4.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "@vitest/ui": "^3.2.4", - "eslint": "^8.57.1", - "globals": "^16.4.0", - "happy-dom": "^20.0.10", - "jiti": "^2.6.1", - "prettier": "^3.6.2", - "react": "^18.3.1 || ^19.0.0", - "react-dom": "^18.3.1 || ^19.0.0", - "rollup": "^4.9.0", - "tslib": "^2.6.2", - "typescript": "^5.3.3", - "typescript-eslint": "^8.46.0", - "uuid": "^11.0.3", - "vitest": "^3.2.4", - "zustand": "^5.0.2" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.0.0", - "react": "^18.0.0 || ^19.0.0", - "zustand": "^4.0.0 || ^5.0.0" - } - }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", @@ -2677,8 +2633,20 @@ } }, "node_modules/@trustgraph/react-state": { - "resolved": "../trustgraph-react-state", - "link": true + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@trustgraph/react-state/-/react-state-1.7.2.tgz", + "integrity": "sha512-cdHjnUVlRnQL1k9yLuOFl28WukN0oyK/kYdegrJRSMMd48/TGyRsKdaRKcfmqCS2glLJNvI06zZU/KvXhmccJQ==", + "license": "MIT", + "dependencies": { + "@trustgraph/react-provider": "^1.4.0", + "compute-cosine-similarity": "^1.1.0", + "uuid": "^11.0.3" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.0.0", + "react": "^18.0.0 || ^19.0.0", + "zustand": "^4.0.0 || ^5.0.0" + } }, "node_modules/@tweenjs/tween.js": { "version": "25.0.0",