feat: chat message actions, explainability graphs, and graph query filters

Add chat UX improvements: message actions toolbar (copy/delete/regenerate)
on hover, inline explainability subgraph visualization from RAG/agent
queries, and token metadata for all chat modes. Enhance graph page with
SPO query filters, configurable triple limit, and type legend overlay.
Extract shared graph utilities for reuse across components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
elpresidank 2026-04-12 02:55:46 -05:00
parent d5dd15be72
commit 87f6e5eb05
7 changed files with 806 additions and 160 deletions

View file

@ -8,7 +8,7 @@ import {
import { useSessionStore } from "./use-session-store";
import { useProgressStore } from "./use-progress-store";
import { useSettings } from "@/providers/settings-provider";
import type { StreamingMetadata } from "@trustgraph/client";
import type { StreamingMetadata, ExplainEvent } from "@trustgraph/client";
// ---------------------------------------------------------------------------
// Hook
@ -17,6 +17,7 @@ import type { StreamingMetadata } from "@trustgraph/client";
export interface UseChatReturn {
submitMessage: (opts: { input: string }) => void;
cancelRequest: () => void;
regenerateLastMessage: () => void;
}
/**
@ -93,6 +94,22 @@ export function useChat(): UseChatReturn {
const flow = socket.flow(flowId);
// Collect explainability events during streaming
const explainEvents: ExplainEvent[] = [];
const onExplain = (event: ExplainEvent) => {
explainEvents.push(event);
};
// Attach collected explain events to the message on completion
const attachExplainEvents = () => {
if (explainEvents.length > 0) {
updateLastMessage((prev) => ({
...prev,
explainEvents: [...explainEvents],
}));
}
};
// Shared handler for streaming responses (graph-rag / document-rag)
const onChunk = (
chunk: string,
@ -115,6 +132,7 @@ export function useChat(): UseChatReturn {
}));
if (complete) {
attachExplainEvents();
removeActivity(activityLabel);
}
};
@ -132,11 +150,11 @@ export function useChat(): UseChatReturn {
// 3. Dispatch based on chat mode
switch (chatMode) {
case "graph-rag":
flow.graphRagStreaming(input, onChunk, onError, undefined, collection);
flow.graphRagStreaming(input, onChunk, onError, undefined, collection, onExplain);
break;
case "document-rag":
flow.documentRagStreaming(input, onChunk, onError, undefined, collection);
flow.documentRagStreaming(input, onChunk, onError, undefined, collection, onExplain);
break;
case "agent": {
@ -212,11 +230,14 @@ export function useChat(): UseChatReturn {
};
});
if (complete) {
attachExplainEvents();
removeActivity(activityLabel);
}
},
// error
onError,
// explainability
onExplain,
);
break;
}
@ -235,5 +256,15 @@ export function useChat(): UseChatReturn {
],
);
return { submitMessage, cancelRequest };
const regenerateLastMessage = useCallback(() => {
const msgs = useConversation.getState().messages;
const lastAssistant = [...msgs].reverse().find((m) => m.role === "assistant");
const lastUser = [...msgs].reverse().find((m) => m.role === "user");
if (lastAssistant && lastUser) {
useConversation.getState().deleteMessage(lastAssistant.id);
submitMessage({ input: lastUser.content });
}
}, [submitMessage]);
return { submitMessage, cancelRequest, regenerateLastMessage };
}