Skip to content

Commit f22f0a8

Browse files
committed
state-and-stm
1 parent 7eb3b45 commit f22f0a8

File tree

4 files changed

+127
-43
lines changed

4 files changed

+127
-43
lines changed

app/src/App.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
44
import { BrowserRouter, Routes, Route } from "react-router-dom";
55
import { AuthProvider } from "@/contexts/AuthContext";
66
import { DatabaseProvider } from "@/contexts/DatabaseContext";
7+
import { ChatProvider } from "@/contexts/ChatContext";
78
import Index from "./pages/Index";
89
import Settings from "./pages/Settings";
910
import NotFound from "./pages/NotFound";
@@ -14,17 +15,19 @@ const App = () => (
1415
<QueryClientProvider client={queryClient}>
1516
<AuthProvider>
1617
<DatabaseProvider>
17-
<TooltipProvider>
18-
<Toaster />
19-
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
20-
<Routes>
21-
<Route path="/" element={<Index />} />
22-
<Route path="/settings" element={<Settings />} />
23-
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
24-
<Route path="*" element={<NotFound />} />
25-
</Routes>
26-
</BrowserRouter>
27-
</TooltipProvider>
18+
<ChatProvider>
19+
<TooltipProvider>
20+
<Toaster />
21+
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
22+
<Routes>
23+
<Route path="/" element={<Index />} />
24+
<Route path="/settings" element={<Settings />} />
25+
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
26+
<Route path="*" element={<NotFound />} />
27+
</Routes>
28+
</BrowserRouter>
29+
</TooltipProvider>
30+
</ChatProvider>
2831
</DatabaseProvider>
2932
</AuthProvider>
3033
</QueryClientProvider>

app/src/components/chat/ChatInterface.tsx

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { useState, useEffect, useRef } from "react";
1+
import { useEffect, useRef } from "react";
22
import { cn } from "@/lib/utils";
33
import { useToast } from "@/components/ui/use-toast";
44
import { useDatabase } from "@/contexts/DatabaseContext";
55
import { useAuth } from "@/contexts/AuthContext";
6+
import { useChat } from "@/contexts/ChatContext";
67
import LoadingSpinner from "@/components/ui/loading-spinner";
78
import { Skeleton } from "@/components/ui/skeleton";
89
import ChatMessage from "./ChatMessage";
910
import QueryInput from "./QueryInput";
1011
import SuggestionCards from "../SuggestionCards";
1112
import { ChatService } from "@/services/chat";
12-
import type { ConversationMessage } from "@/types/api";
1313

1414
interface ChatMessageData {
1515
id: string;
@@ -53,10 +53,9 @@ const ChatInterface = ({
5353
}: ChatInterfaceProps) => {
5454
const { toast } = useToast();
5555
const { selectedGraph } = useDatabase();
56-
const [isProcessing, setIsProcessing] = useState(false);
56+
const { messages, setMessages, conversationHistory, isProcessing, setIsProcessing } = useChat();
5757
const messagesEndRef = useRef<HTMLDivElement>(null);
5858
const chatContainerRef = useRef<HTMLDivElement>(null);
59-
const conversationHistory = useRef<ConversationMessage[]>([]);
6059

6160
// Auto-scroll to bottom function
6261
const scrollToBottom = () => {
@@ -80,36 +79,13 @@ const ChatInterface = ({
8079
);
8180

8281
const { user } = useAuth();
83-
const [messages, setMessages] = useState<ChatMessageData[]>([
84-
{
85-
id: "1",
86-
type: "ai",
87-
content: "Hello! Describe what you'd like to ask your database",
88-
timestamp: new Date(),
89-
}
90-
]);
9182

9283
const suggestions = [
9384
"Show me five customers",
9485
"Show me the top customers by revenue",
9586
"What are the pending orders?"
9687
];
9788

98-
// Reset conversation when the selected graph changes to avoid leaking
99-
// conversation history between different databases.
100-
useEffect(() => {
101-
// Clear in-memory conversation history and reset messages to the greeting
102-
conversationHistory.current = [];
103-
setMessages([
104-
{
105-
id: "1",
106-
type: "ai",
107-
content: "Hello! Describe what you'd like to ask your database",
108-
timestamp: new Date(),
109-
}
110-
]);
111-
}, [selectedGraph?.id]);
112-
11389
// Scroll to bottom whenever messages change
11490
useEffect(() => {
11591
scrollToBottom();

app/src/contexts/ChatContext.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { createContext, useContext, useState, useRef, useCallback, useEffect } from 'react';
2+
import { useDatabase } from '@/contexts/DatabaseContext';
3+
import type { ConversationMessage } from '@/types/api';
4+
5+
interface ChatMessageData {
6+
id: string;
7+
type: 'user' | 'ai' | 'ai-steps' | 'sql-query' | 'query-result' | 'confirmation';
8+
content: string;
9+
steps?: Array<{
10+
icon: 'search' | 'database' | 'code' | 'message';
11+
text: string;
12+
}>;
13+
queryData?: any[];
14+
analysisInfo?: {
15+
confidence?: number;
16+
missing?: string;
17+
ambiguities?: string;
18+
explanation?: string;
19+
isValid?: boolean;
20+
};
21+
confirmationData?: {
22+
sqlQuery: string;
23+
operationType: string;
24+
message: string;
25+
chatHistory: string[];
26+
};
27+
timestamp: Date;
28+
}
29+
30+
interface ChatContextType {
31+
messages: ChatMessageData[];
32+
setMessages: React.Dispatch<React.SetStateAction<ChatMessageData[]>>;
33+
conversationHistory: React.MutableRefObject<ConversationMessage[]>;
34+
isProcessing: boolean;
35+
setIsProcessing: React.Dispatch<React.SetStateAction<boolean>>;
36+
resetChat: () => void;
37+
}
38+
39+
const initialMessage: ChatMessageData = {
40+
id: "1",
41+
type: "ai",
42+
content: "Hello! Describe what you'd like to ask your database",
43+
timestamp: new Date(),
44+
};
45+
46+
const ChatContext = createContext<ChatContextType | undefined>(undefined);
47+
48+
export const ChatProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
49+
const { selectedGraph } = useDatabase();
50+
const [messages, setMessages] = useState<ChatMessageData[]>([initialMessage]);
51+
const [isProcessing, setIsProcessing] = useState(false);
52+
const conversationHistory = useRef<ConversationMessage[]>([]);
53+
const previousGraphIdRef = useRef<string | undefined>(undefined);
54+
55+
// Reset conversation when the selected graph changes to avoid leaking
56+
// conversation history between different databases.
57+
useEffect(() => {
58+
// Only reset if the graph actually changed (not on initial mount with same graph)
59+
if (previousGraphIdRef.current !== undefined && previousGraphIdRef.current !== selectedGraph?.id) {
60+
conversationHistory.current = [];
61+
setMessages([{
62+
...initialMessage,
63+
id: Date.now().toString(),
64+
timestamp: new Date(),
65+
}]);
66+
}
67+
previousGraphIdRef.current = selectedGraph?.id;
68+
}, [selectedGraph?.id]);
69+
70+
const resetChat = useCallback(() => {
71+
conversationHistory.current = [];
72+
setMessages([{
73+
...initialMessage,
74+
id: Date.now().toString(),
75+
timestamp: new Date(),
76+
}]);
77+
}, []);
78+
79+
return (
80+
<ChatContext.Provider value={{
81+
messages,
82+
setMessages,
83+
conversationHistory,
84+
isProcessing,
85+
setIsProcessing,
86+
resetChat,
87+
}}>
88+
{children}
89+
</ChatContext.Provider>
90+
);
91+
};
92+
93+
export const useChat = () => {
94+
const context = useContext(ChatContext);
95+
if (context === undefined) {
96+
throw new Error('useChat must be used within a ChatProvider');
97+
}
98+
return context;
99+
};

app/src/services/chat.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ export class ChatService {
1717
const endpoint = `/graphs/${encodeURIComponent(request.database)}`;
1818

1919
// Transform conversation history to backend format
20-
// Backend expects: chat: ["user msg", "ai msg", "user msg", ...]
20+
// Backend expects:
21+
// - chat: list of user queries only (strings)
22+
// - result: list of AI responses (strings)
2123
const chatHistory: string[] = [];
24+
const resultHistory: string[] = [];
25+
2226
if (request.history && request.history.length > 0) {
2327
for (const msg of request.history) {
24-
chatHistory.push(msg.content);
28+
if (msg.role === 'user') {
29+
chatHistory.push(msg.content);
30+
} else if (msg.role === 'assistant') {
31+
resultHistory.push(msg.content);
32+
}
2533
}
2634
}
2735
// Add current query to the chat array
@@ -34,9 +42,7 @@ export class ChatService {
3442
},
3543
body: JSON.stringify({
3644
chat: chatHistory,
37-
// Optional fields the backend supports:
38-
// result: [], // Previous results if needed
39-
// instructions: "" // Additional instructions if needed
45+
result: resultHistory.length > 0 ? resultHistory : undefined,
4046
...(request.use_user_rules !== undefined && {
4147
use_user_rules: request.use_user_rules
4248
}),

0 commit comments

Comments
 (0)