fix: comprehensive QA — resolve 13 bugs, add UX improvements across all services
Client SDK: add .catch() to graphRagStreaming/documentRagStreaming (silent timeout),
null-guard JSON.parse in getPrompts/getSystemPrompt/getPrompt.
Backend: implement "getvalues" config operation for token costs, null-check
createTerm() in FalkorDB triples query, add knowledge-cores service entrypoint
and Docker entry, return proper HTTP 400/404 for gateway error responses.
Workbench: cancel button + elapsed timer for chat, clear agent spinner on error,
flow dialog inline validation, responsive header wrapping, knowledge cores
loading timeout, sidebar/page naming consistency, theme toggle indicator.
Infrastructure: enable Grafana Explore for viewers, add gateway Prometheus
scrape target, fix RAG pipeline dashboard layout (6 panels visible),
filter Service Health to configured targets only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 05:20:10 -05:00
|
|
|
import { useCallback, useRef } from "react";
|
2026-04-05 22:44:45 -05:00
|
|
|
import { useSocket } from "@/providers/socket-provider";
|
|
|
|
|
import {
|
|
|
|
|
useConversation,
|
|
|
|
|
nextMessageId,
|
|
|
|
|
type ChatMessage,
|
|
|
|
|
} from "./use-conversation";
|
|
|
|
|
import { useSessionStore } from "./use-session-store";
|
|
|
|
|
import { useProgressStore } from "./use-progress-store";
|
|
|
|
|
import { useSettings } from "@/providers/settings-provider";
|
2026-04-12 02:55:46 -05:00
|
|
|
import type { StreamingMetadata, ExplainEvent } from "@trustgraph/client";
|
2026-04-05 22:44:45 -05:00
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Hook
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
export interface UseChatReturn {
|
|
|
|
|
submitMessage: (opts: { input: string }) => void;
|
fix: comprehensive QA — resolve 13 bugs, add UX improvements across all services
Client SDK: add .catch() to graphRagStreaming/documentRagStreaming (silent timeout),
null-guard JSON.parse in getPrompts/getSystemPrompt/getPrompt.
Backend: implement "getvalues" config operation for token costs, null-check
createTerm() in FalkorDB triples query, add knowledge-cores service entrypoint
and Docker entry, return proper HTTP 400/404 for gateway error responses.
Workbench: cancel button + elapsed timer for chat, clear agent spinner on error,
flow dialog inline validation, responsive header wrapping, knowledge cores
loading timeout, sidebar/page naming consistency, theme toggle indicator.
Infrastructure: enable Grafana Explore for viewers, add gateway Prometheus
scrape target, fix RAG pipeline dashboard layout (6 panels visible),
filter Service Health to configured targets only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 05:20:10 -05:00
|
|
|
cancelRequest: () => void;
|
2026-04-12 02:55:46 -05:00
|
|
|
regenerateLastMessage: () => void;
|
2026-04-05 22:44:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Orchestrates sending a chat message through the selected RAG / agent
|
|
|
|
|
* pipeline and accumulates streamed chunks into the conversation store.
|
|
|
|
|
*/
|
|
|
|
|
export function useChat(): UseChatReturn {
|
|
|
|
|
const socket = useSocket();
|
|
|
|
|
const flowId = useSessionStore((s) => s.flowId);
|
|
|
|
|
const chatMode = useConversation((s) => s.chatMode);
|
|
|
|
|
const addMessage = useConversation((s) => s.addMessage);
|
|
|
|
|
const updateLastMessage = useConversation((s) => s.updateLastMessage);
|
|
|
|
|
const setInput = useConversation((s) => s.setInput);
|
|
|
|
|
const collection = useSettings((s) => s.settings.collection);
|
|
|
|
|
const addActivity = useProgressStore((s) => s.addActivity);
|
|
|
|
|
const removeActivity = useProgressStore((s) => s.removeActivity);
|
|
|
|
|
|
fix: comprehensive QA — resolve 13 bugs, add UX improvements across all services
Client SDK: add .catch() to graphRagStreaming/documentRagStreaming (silent timeout),
null-guard JSON.parse in getPrompts/getSystemPrompt/getPrompt.
Backend: implement "getvalues" config operation for token costs, null-check
createTerm() in FalkorDB triples query, add knowledge-cores service entrypoint
and Docker entry, return proper HTTP 400/404 for gateway error responses.
Workbench: cancel button + elapsed timer for chat, clear agent spinner on error,
flow dialog inline validation, responsive header wrapping, knowledge cores
loading timeout, sidebar/page naming consistency, theme toggle indicator.
Infrastructure: enable Grafana Explore for viewers, add gateway Prometheus
scrape target, fix RAG pipeline dashboard layout (6 panels visible),
filter Service Health to configured targets only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 05:20:10 -05:00
|
|
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
|
|
|
|
|
|
|
|
const cancelRequest = useCallback(() => {
|
|
|
|
|
if (abortControllerRef.current) {
|
|
|
|
|
abortControllerRef.current.abort();
|
|
|
|
|
abortControllerRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
updateLastMessage((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
content: prev.content || "(Cancelled)",
|
|
|
|
|
isStreaming: false,
|
|
|
|
|
activePhase: undefined,
|
|
|
|
|
}));
|
|
|
|
|
removeActivity("Chat request");
|
|
|
|
|
}, [updateLastMessage, removeActivity]);
|
|
|
|
|
|
2026-04-05 22:44:45 -05:00
|
|
|
const submitMessage = useCallback(
|
|
|
|
|
({ input }: { input: string }) => {
|
|
|
|
|
if (!input.trim()) return;
|
|
|
|
|
|
fix: comprehensive QA — resolve 13 bugs, add UX improvements across all services
Client SDK: add .catch() to graphRagStreaming/documentRagStreaming (silent timeout),
null-guard JSON.parse in getPrompts/getSystemPrompt/getPrompt.
Backend: implement "getvalues" config operation for token costs, null-check
createTerm() in FalkorDB triples query, add knowledge-cores service entrypoint
and Docker entry, return proper HTTP 400/404 for gateway error responses.
Workbench: cancel button + elapsed timer for chat, clear agent spinner on error,
flow dialog inline validation, responsive header wrapping, knowledge cores
loading timeout, sidebar/page naming consistency, theme toggle indicator.
Infrastructure: enable Grafana Explore for viewers, add gateway Prometheus
scrape target, fix RAG pipeline dashboard layout (6 panels visible),
filter Service Health to configured targets only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 05:20:10 -05:00
|
|
|
// Abort any in-flight request
|
|
|
|
|
if (abortControllerRef.current) {
|
|
|
|
|
abortControllerRef.current.abort();
|
|
|
|
|
}
|
|
|
|
|
abortControllerRef.current = new AbortController();
|
|
|
|
|
|
2026-04-05 22:44:45 -05:00
|
|
|
const activityLabel = "Chat request";
|
|
|
|
|
|
|
|
|
|
// 1. Add the user message
|
|
|
|
|
const userMsg: ChatMessage = {
|
|
|
|
|
id: nextMessageId(),
|
|
|
|
|
role: "user",
|
|
|
|
|
content: input,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
};
|
|
|
|
|
addMessage(userMsg);
|
|
|
|
|
setInput("");
|
|
|
|
|
|
|
|
|
|
// 2. Add a placeholder assistant message for streaming
|
|
|
|
|
const assistantId = nextMessageId();
|
|
|
|
|
const isAgent = chatMode === "agent";
|
|
|
|
|
const assistantMsg: ChatMessage = {
|
|
|
|
|
id: assistantId,
|
|
|
|
|
role: "assistant",
|
|
|
|
|
content: "",
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
isStreaming: true,
|
|
|
|
|
...(isAgent
|
|
|
|
|
? {
|
|
|
|
|
agentPhases: { think: "", observe: "", answer: "" },
|
|
|
|
|
activePhase: "think" as const,
|
|
|
|
|
}
|
|
|
|
|
: {}),
|
|
|
|
|
};
|
|
|
|
|
addMessage(assistantMsg);
|
|
|
|
|
addActivity(activityLabel);
|
|
|
|
|
|
|
|
|
|
const flow = socket.flow(flowId);
|
|
|
|
|
|
2026-04-12 02:55:46 -05:00
|
|
|
// 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],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-05 22:44:45 -05:00
|
|
|
// Shared handler for streaming responses (graph-rag / document-rag)
|
|
|
|
|
const onChunk = (
|
|
|
|
|
chunk: string,
|
|
|
|
|
complete: boolean,
|
|
|
|
|
metadata?: StreamingMetadata,
|
|
|
|
|
) => {
|
|
|
|
|
updateLastMessage((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
content: prev.content + chunk,
|
|
|
|
|
isStreaming: !complete,
|
|
|
|
|
...(complete && metadata
|
|
|
|
|
? {
|
|
|
|
|
metadata: {
|
|
|
|
|
model: metadata.model,
|
|
|
|
|
inTokens: metadata.in_token,
|
|
|
|
|
outTokens: metadata.out_token,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
: {}),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (complete) {
|
2026-04-12 02:55:46 -05:00
|
|
|
attachExplainEvents();
|
2026-04-05 22:44:45 -05:00
|
|
|
removeActivity(activityLabel);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onError = (error: string) => {
|
|
|
|
|
updateLastMessage((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
content: prev.content || `Error: ${error}`,
|
|
|
|
|
isStreaming: false,
|
fix: comprehensive QA — resolve 13 bugs, add UX improvements across all services
Client SDK: add .catch() to graphRagStreaming/documentRagStreaming (silent timeout),
null-guard JSON.parse in getPrompts/getSystemPrompt/getPrompt.
Backend: implement "getvalues" config operation for token costs, null-check
createTerm() in FalkorDB triples query, add knowledge-cores service entrypoint
and Docker entry, return proper HTTP 400/404 for gateway error responses.
Workbench: cancel button + elapsed timer for chat, clear agent spinner on error,
flow dialog inline validation, responsive header wrapping, knowledge cores
loading timeout, sidebar/page naming consistency, theme toggle indicator.
Infrastructure: enable Grafana Explore for viewers, add gateway Prometheus
scrape target, fix RAG pipeline dashboard layout (6 panels visible),
filter Service Health to configured targets only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 05:20:10 -05:00
|
|
|
activePhase: undefined,
|
2026-04-05 22:44:45 -05:00
|
|
|
}));
|
|
|
|
|
removeActivity(activityLabel);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 3. Dispatch based on chat mode
|
|
|
|
|
switch (chatMode) {
|
|
|
|
|
case "graph-rag":
|
2026-04-12 02:55:46 -05:00
|
|
|
flow.graphRagStreaming(input, onChunk, onError, undefined, collection, onExplain);
|
2026-04-05 22:44:45 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "document-rag":
|
2026-04-12 02:55:46 -05:00
|
|
|
flow.documentRagStreaming(input, onChunk, onError, undefined, collection, onExplain);
|
2026-04-05 22:44:45 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "agent": {
|
|
|
|
|
// Agent has separate think / observe / answer streams.
|
|
|
|
|
// We track each phase in agentPhases and display the answer
|
|
|
|
|
// as the main content.
|
|
|
|
|
|
|
|
|
|
flow.agent(
|
|
|
|
|
input,
|
|
|
|
|
// think
|
|
|
|
|
(chunk, complete) => {
|
|
|
|
|
updateLastMessage((prev) => {
|
|
|
|
|
const phases = prev.agentPhases ?? {
|
|
|
|
|
think: "",
|
|
|
|
|
observe: "",
|
|
|
|
|
answer: "",
|
|
|
|
|
};
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
agentPhases: {
|
|
|
|
|
...phases,
|
|
|
|
|
think: phases.think + chunk,
|
|
|
|
|
},
|
|
|
|
|
activePhase: complete ? prev.activePhase : "think",
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
// observe
|
|
|
|
|
(chunk, complete) => {
|
|
|
|
|
updateLastMessage((prev) => {
|
|
|
|
|
const phases = prev.agentPhases ?? {
|
|
|
|
|
think: "",
|
|
|
|
|
observe: "",
|
|
|
|
|
answer: "",
|
|
|
|
|
};
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
agentPhases: {
|
|
|
|
|
...phases,
|
|
|
|
|
observe: phases.observe + chunk,
|
|
|
|
|
},
|
|
|
|
|
activePhase: complete ? prev.activePhase : "observe",
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
// answer
|
|
|
|
|
(chunk, complete, metadata) => {
|
|
|
|
|
updateLastMessage((prev) => {
|
|
|
|
|
const phases = prev.agentPhases ?? {
|
|
|
|
|
think: "",
|
|
|
|
|
observe: "",
|
|
|
|
|
answer: "",
|
|
|
|
|
};
|
|
|
|
|
const newAnswer = phases.answer + chunk;
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
content: newAnswer,
|
|
|
|
|
agentPhases: {
|
|
|
|
|
...phases,
|
|
|
|
|
answer: newAnswer,
|
|
|
|
|
},
|
|
|
|
|
activePhase: complete ? undefined : "answer",
|
|
|
|
|
isStreaming: !complete,
|
|
|
|
|
...(complete && metadata
|
|
|
|
|
? {
|
|
|
|
|
metadata: {
|
|
|
|
|
model: metadata.model,
|
|
|
|
|
inTokens: metadata.in_token,
|
|
|
|
|
outTokens: metadata.out_token,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
: {}),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
if (complete) {
|
2026-04-12 02:55:46 -05:00
|
|
|
attachExplainEvents();
|
2026-04-05 22:44:45 -05:00
|
|
|
removeActivity(activityLabel);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// error
|
|
|
|
|
onError,
|
2026-04-12 02:55:46 -05:00
|
|
|
// explainability
|
|
|
|
|
onExplain,
|
2026-04-05 22:44:45 -05:00
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[
|
|
|
|
|
socket,
|
|
|
|
|
flowId,
|
|
|
|
|
chatMode,
|
|
|
|
|
collection,
|
|
|
|
|
addMessage,
|
|
|
|
|
updateLastMessage,
|
|
|
|
|
setInput,
|
|
|
|
|
addActivity,
|
|
|
|
|
removeActivity,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-12 02:55:46 -05:00
|
|
|
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 };
|
2026-04-05 22:44:45 -05:00
|
|
|
}
|