Skip to content
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: 5 additions & 65 deletions app/package-lock.json

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

2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@falkordb/canvas": "^0.0.45",
"@falkordb/canvas": "^0.2.7",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"@hookform/resolvers": "^5.7.1",
"@radix-ui/react-accordion": "^1.2.20",
"@radix-ui/react-alert-dialog": "^1.1.23",
Expand Down
25 changes: 14 additions & 11 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthProvider } from "@/contexts/AuthContext";
import { DatabaseProvider } from "@/contexts/DatabaseContext";
import { SettingsProvider } from "@/contexts/SettingsContext";
import { ChatProvider } from "@/contexts/ChatContext";
import { QueryHighlightProvider } from "@/contexts/QueryHighlightContext";
import Index from "./pages/Index";
import Settings from "./pages/Settings";
import NotFound from "./pages/NotFound";
Expand All @@ -18,17 +19,19 @@ const App = () => (
<DatabaseProvider>
<SettingsProvider>
<ChatProvider>
<TooltipProvider>
<Toaster />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/settings" element={<Settings />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
<QueryHighlightProvider>
<TooltipProvider>
<Toaster />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/settings" element={<Settings />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
</QueryHighlightProvider>
</ChatProvider>
</SettingsProvider>
</DatabaseProvider>
Expand Down
4 changes: 4 additions & 0 deletions app/src/components/chat/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useDatabase } from "@/contexts/DatabaseContext";
import { useAuth } from "@/contexts/AuthContext";
import { useSettings } from "@/contexts/SettingsContext";
import { useChat } from "@/contexts/ChatContext";
import { useQueryHighlight } from "@/contexts/QueryHighlightContext";
import LoadingSpinner from "@/components/ui/loading-spinner";
import { Skeleton } from "@/components/ui/skeleton";
import ChatMessage from "./ChatMessage";
Expand Down Expand Up @@ -58,6 +59,7 @@ const ChatInterface = ({
const { selectedGraph } = useDatabase();
const { vendor, apiKey, modelName, isApiKeyValid } = useSettings();
const { messages, setMessages, conversationHistory, isProcessing, setIsProcessing } = useChat();
const { selectedQueryId, toggleQueryHighlight } = useQueryHighlight();
const messagesEndRef = useRef<HTMLDivElement>(null);
const chatContainerRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -510,6 +512,8 @@ const ChatInterface = ({
analysisInfo={msg.analysisInfo}
confirmationData={msg.confirmationData}
user={user}
isQueryHighlighted={msg.type === 'sql-query' && selectedQueryId === msg.id}
onToggleQueryHighlight={msg.type === 'sql-query' ? () => toggleQueryHighlight(msg.id, msg.content) : undefined}
onConfirm={msg.type === 'confirmation' ? () => handleConfirmDestructive(msg.id) : undefined}
onCancel={msg.type === 'confirmation' ? () => handleCancelDestructive(msg.id) : undefined}
/>
Expand Down
44 changes: 42 additions & 2 deletions app/src/components/chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ interface ChatMessageProps {
};
progress?: number; // Progress percentage for AI steps
user?: UserType | null; // User info for avatar
isQueryHighlighted?: boolean; // Whether this query's tables are highlighted in the schema canvas
onToggleQueryHighlight?: () => void; // Select/unselect this query to highlight it in the schema canvas
onConfirm?: () => void;
onCancel?: () => void;
}

const ChatMessage = ({ type, content, steps, queryData, analysisInfo, confirmationData, progress, user, onConfirm, onCancel }: ChatMessageProps) => {
const ChatMessage = ({ type, content, steps, queryData, analysisInfo, confirmationData, progress, user, isQueryHighlighted, onToggleQueryHighlight, onConfirm, onCancel }: ChatMessageProps) => {
const [copied, setCopied] = useState(false);

const handleCopyQuery = async () => {
Expand All @@ -48,6 +50,13 @@ const ChatMessage = ({ type, content, steps, queryData, analysisInfo, confirmati
}
};

// Clicking the query toggles the schema highlight, but a click that ends a
// text selection must not steal the selection from the user.
const handleQueryBlockClick = () => {
if (window.getSelection()?.toString()) return;
onToggleQueryHighlight?.();
};

if (type === 'confirmation') {
const operationType = (confirmationData?.operationType ?? 'UNKNOWN').toUpperCase();
const isHighRisk = ['DELETE', 'DROP', 'TRUNCATE'].includes(operationType);
Expand Down Expand Up @@ -148,6 +157,7 @@ const ChatMessage = ({ type, content, steps, queryData, analysisInfo, confirmati
if (type === 'sql-query') {
const hasSQL = content && content.trim().length > 0;
const isValid = analysisInfo?.isValid !== false; // Default to true if not specified
const isClickable = hasSQL && Boolean(onToggleQueryHighlight);

return (
<div className="px-6" data-testid="sql-query-message">
Expand All @@ -165,6 +175,23 @@ const ChatMessage = ({ type, content, steps, queryData, analysisInfo, confirmati
<span className={`text-base font-semibold ${isValid ? 'text-primary' : 'text-warning'}`}>
{hasSQL ? 'Generated SQL Query' : 'Query Analysis'}
</span>
{isQueryHighlighted && (
<Badge variant="outline" className="ml-auto text-xs border-primary text-primary">
Shown in schema
</Badge>
)}
{isClickable && (
<Button
variant="ghost"
size="sm"
data-testid="sql-highlight-toggle"
aria-pressed={isQueryHighlighted}
onClick={onToggleQueryHighlight}
className={`h-7 px-2 text-xs ${isQueryHighlighted ? '' : 'ml-auto'}`}
>
{isQueryHighlighted ? 'Clear schema highlight' : 'Show in schema'}
</Button>
)}
</div>

{hasSQL && (
Expand All @@ -183,9 +210,22 @@ const ChatMessage = ({ type, content, steps, queryData, analysisInfo, confirmati
<Copy className="w-4 h-4 text-muted-foreground" />
)}
</Button>
<pre className="bg-background text-foreground p-3 rounded text-sm mb-3 w-fit min-w-full font-mono whitespace-pre-wrap break-words overflow-wrap-anywhere">
<pre
data-testid="sql-query-block"
onClick={isClickable ? handleQueryBlockClick : undefined}
className={`bg-background text-foreground p-3 pr-12 rounded text-sm mb-1 w-fit min-w-full font-mono whitespace-pre-wrap break-words overflow-wrap-anywhere transition-colors ${
isClickable ? 'cursor-pointer hover:ring-1 hover:ring-primary/50' : ''
} ${isQueryHighlighted ? 'ring-2 ring-primary bg-primary/5' : ''}`}
>
<code className="language-sql">{content}</code>
</pre>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{isClickable && (
<p className="text-xs text-muted-foreground mb-3">
{isQueryHighlighted
? 'Click the query again to clear the schema highlight.'
: 'Click the query to highlight its tables and relations in the schema.'}
</p>
)}
</div>
</div>
)}
Expand Down
Loading
Loading