Skip to content
This repository was archived by the owner on Jun 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 35 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 2 additions & 7 deletions src/components/chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

Expand Down
19 changes: 14 additions & 5 deletions src/components/chat/ExplainabilityPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ExplorationSection,
FocusSection,
SourceDrawer,
AgentExplainTimeline,
} from "./explainability";

interface ExplainabilityPanelProps {
Expand All @@ -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);
Expand Down Expand Up @@ -67,17 +70,23 @@ const ExplainabilityPanel = ({ sessionId }: ExplainabilityPanelProps) => {
<Lightbulb size={12} />
</Badge>
<Text fontSize="sm" color="fg.muted">
How I found this answer
{hasAgentSteps ? "How the agent reasoned" : "How I found this answer"}
</Text>
</Flex>
</Collapsible.Trigger>

<Collapsible.Content>
<Box pl={8} pr={2} py={2}>
{session.question && <QuestionSection question={session.question} />}
{session.exploration && <ExplorationSection exploration={session.exploration} />}
{session.focus && (
<FocusSection focus={session.focus} onSourceClick={handleSourceClick} />
{hasAgentSteps ? (
<AgentExplainTimeline steps={session.agentSteps!} />
) : (
<>
{session.question && <QuestionSection question={session.question} />}
{session.exploration && <ExplorationSection exploration={session.exploration} />}
{session.focus && (
<FocusSection focus={session.focus} onSourceClick={handleSourceClick} />
)}
</>
)}
</Box>
</Collapsible.Content>
Expand Down
122 changes: 122 additions & 0 deletions src/components/chat/explainability/AgentExplainTimeline.tsx
Original file line number Diff line number Diff line change
@@ -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: <HelpCircle size={12} />,
defaultLabel: "Question",
},
decomposition: {
color: "yellow",
icon: <GitBranch size={12} />,
defaultLabel: "Decomposition",
},
analysis: {
color: "purple",
icon: <Wrench size={12} />,
defaultLabel: "Analysis",
},
reflection: {
color: "cyan",
icon: <MessageSquare size={12} />,
defaultLabel: "Observation",
},
conclusion: {
color: "green",
icon: <CheckCircle size={12} />,
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 ? (
<Text fontSize="xs" fontStyle="italic">"{q}"</Text>
) : null;
}
case "decomposition": {
const goals = (step as DecompositionEvent).goals;
return goals.length > 0 ? (
<Box pl={2}>
{goals.map((g, i) => (
<Text key={i} fontSize="xs" color="fg.muted">- {g}</Text>
))}
</Box>
) : null;
}
case "analysis": {
const a = step as AnalysisEvent;
return a.action ? (
<Text fontSize="xs" color="fg.muted">
{a.action}
{a.arguments && (
<Text as="span" fontSize="xs" color="fg.subtle" ml={1}>
{a.arguments.length > 80
? a.arguments.substring(0, 80) + "..."
: a.arguments}
</Text>
)}
</Text>
) : null;
}
default:
return null;
}
};

interface AgentExplainTimelineProps {
steps: StructuredExplainEvent[];
}

const AgentExplainTimeline = ({ steps }: AgentExplainTimelineProps) => {
if (steps.length === 0) return null;

return (
<VStack gap={1} align="stretch">
{steps.map((step, i) => {
const config = STEP_CONFIG[step.type] || STEP_CONFIG.reflection;
const label =
("label" in step && step.label) || config.defaultLabel;

return (
<Box key={i} py={1}>
<Flex align="center" gap={2}>
<Badge
size="sm"
colorPalette={config.color}
variant="subtle"
>
{config.icon}
<Text ml={1}>{label}</Text>
</Badge>
</Flex>
<Box pl={2} mt={0.5}>
<StepDetail step={step} />
</Box>
</Box>
);
})}
</VStack>
);
};

export default AgentExplainTimeline;
1 change: 1 addition & 0 deletions src/components/chat/explainability/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Loading