Skip to content

Commit d0de621

Browse files
authored
Merge pull request #1574 from FalkorDB/fix-ai-comments
Refactor code structure for improved readability and maintainability
2 parents 896a360 + 8e9d8d8 commit d0de621

File tree

13 files changed

+979
-899
lines changed

13 files changed

+979
-899
lines changed

.github/workflows/nextjs.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jobs:
8585

8686
- name: Run Trivy vulnerability scanner
8787
id: trivy-scan
88-
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
88+
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
8989
continue-on-error: true
9090
with:
9191
image-ref: 'falkordb/falkordb-browser:test'
@@ -118,7 +118,7 @@ jobs:
118118

119119
- name: Generate Trivy report for PR
120120
if: github.event_name == 'pull_request'
121-
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
121+
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
122122
continue-on-error: true
123123
with:
124124
image-ref: 'falkordb/falkordb-browser:test'
@@ -176,7 +176,7 @@ jobs:
176176
}
177177
178178
- name: Enforce HIGH/CRITICAL vulnerability threshold
179-
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
179+
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
180180
with:
181181
image-ref: 'falkordb/falkordb-browser:test'
182182
format: 'table'

app/api/connection-info/route.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export async function OPTIONS(request: Request) {
77
}
88

99
function parseInfoField(info: string, field: string): string | undefined {
10-
const match = info.match(new RegExp(`${field}:(.+?)\\r?\\n`));
10+
const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
11+
const match = info.match(new RegExp(`${escaped}:(.+?)\\r?\\n`));
1112
return match?.[1]?.trim();
1213
}
1314

@@ -43,10 +44,17 @@ export async function GET(request: Request) {
4344
const [ip, portStr] = address.split(":");
4445
const host = hostname || ip;
4546
const flags = parts[2] ?? "";
46-
const nodeRole = flags.includes("master") ? "master" : "slave";
47+
const flagList = flags.split(",");
48+
// Skip nodes with non-standard flags (e.g., fail, handshake, noaddr)
49+
const isMaster = flagList.includes("master") || flagList.includes("myself,master");
50+
const isSlave = flagList.includes("slave") || flagList.includes("myself,slave");
51+
if (!isMaster && !isSlave) return null;
52+
const nodeRole = isMaster ? "master" : "slave";
53+
const port = Number(portStr);
54+
if (!Number.isFinite(port) || port <= 0) return null;
4755
const slots = nodeRole === "master" ? parts.slice(8).join(" ") : undefined;
48-
return { host, port: Number(portStr), role: nodeRole, slots };
49-
});
56+
return { host, port, role: nodeRole, slots };
57+
}).filter(Boolean);
5058
} catch (err) {
5159
console.error("Failed to get cluster details:", err);
5260
}
@@ -56,7 +64,8 @@ export async function GET(request: Request) {
5664
} else if (role === "slave") {
5765
result.sentinelRole = "slave";
5866
result.sentinelMasterHost = parseInfoField(replicationInfo, "master_host");
59-
result.sentinelMasterPort = Number(parseInfoField(replicationInfo, "master_port") ?? "0");
67+
const parsedPort = Number(parseInfoField(replicationInfo, "master_port") ?? "0");
68+
result.sentinelMasterPort = Number.isFinite(parsedPort) ? parsedPort : 0;
6069
}
6170

6271
return NextResponse.json({ result }, { status: 200, headers: getCorsHeaders(request) });

app/api/validate-body.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,14 @@ export const updateGraphConfig = z.object({
207207
});
208208

209209
// Chat schemas
210+
const chatMessage = z.object({
211+
role: z.string().min(1),
212+
content: z.string(),
213+
});
214+
210215
export const chatRequest = z.object({
211216
messages: z
212-
.array(z.any(), {
217+
.array(chatMessage, {
213218
error: (issue) => issue.input === undefined ? "Messages are required" : "Invalid Messages",
214219
})
215220
.min(1, "Messages are required"),

app/components/Header.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,19 @@ export default function Header({ onSetGraphName, graphNames, graphName, onOpenPa
9191
}, 150);
9292
}, []);
9393

94+
useEffect(() => {
95+
return () => {
96+
if (closeTimeoutRef.current) {
97+
clearTimeout(closeTimeoutRef.current);
98+
}
99+
};
100+
}, []);
101+
94102
const handleCopy = useCallback((text: string) => {
103+
if (!navigator.clipboard?.writeText) {
104+
toast({ title: "Clipboard not available", variant: "destructive" });
105+
return;
106+
}
95107
navigator.clipboard.writeText(text)
96108
.then(() => toast({ title: "Copied to clipboard" }))
97109
.catch(() => toast({ title: "Failed to copy", variant: "destructive" }));

app/globals.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@
174174
}
175175

176176
input:focus-visible {
177-
outline: 2px solid hsl(var(--primary));
178-
outline-offset: 2px;
177+
outline: none;
179178
}
180179

181180
input[type="color"]::-webkit-color-swatch-wrapper {

app/graph/Chat.tsx

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable no-case-declarations */
22
/* eslint-disable react/no-array-index-key */
33
import { cn, getTheme, Message } from "@/lib/utils";
4-
import { useContext, useEffect, useState } from "react";
4+
import { useContext, useEffect, useRef, useState, useCallback } from "react";
55
import { useTheme } from "next-themes";
66
import Image from "next/image";
77
import { ChevronDown, ChevronRight, Share2, Copy, Loader2, Play, Search, X, Send, MessagesSquare } from "lucide-react";
@@ -39,8 +39,8 @@ interface Props {
3939
}
4040

4141
export default function Chat({ onClose }: Props) {
42-
const { theme } = useTheme();
43-
const { currentTheme } = getTheme(theme);
42+
const { resolvedTheme } = useTheme();
43+
const { currentTheme } = getTheme(resolvedTheme);
4444
const { setIndicator } = useContext(IndicatorContext);
4545
const { graphName, runQuery } = useContext(GraphContext);
4646
const { isQueryLoading } = useContext(QueryLoadingContext);
@@ -51,12 +51,60 @@ export default function Chat({ onClose }: Props) {
5151
const { toast } = useToast();
5252
const route = useRouter();
5353

54+
const [mounted, setMounted] = useState(false);
5455
const [messages, setMessages] = useState<Message[]>([]);
5556
const [messagesList, setMessagesList] = useState<(Message | [Message[], boolean])[]>([]);
5657
const [newMessage, setNewMessage] = useState("");
5758
const [isLoading, setIsLoading] = useState(false);
5859
const [queryCollapse, setQueryCollapse] = useState<{ [key: string]: boolean }>({});
5960
const [collapseEligible, setCollapseEligible] = useState<{ [key: number]: boolean }>({});
61+
const textRefs = useRef<Map<number, HTMLElement>>(new Map());
62+
const observerRef = useRef<ResizeObserver | null>(null);
63+
const queryCollapseRef = useRef(queryCollapse);
64+
queryCollapseRef.current = queryCollapse;
65+
66+
useEffect(() => {
67+
setMounted(true);
68+
}, []);
69+
70+
// Create a single ResizeObserver that recomputes collapse eligibility on resize
71+
useEffect(() => {
72+
observerRef.current = new ResizeObserver((entries) => {
73+
for (const entry of entries) {
74+
const el = entry.target as HTMLElement;
75+
textRefs.current.forEach((ref, i) => {
76+
if (ref !== el) return;
77+
const shouldCollapse = el.scrollHeight > 64;
78+
// Only upgrade to eligible, never downgrade — collapsed items have small height
79+
if (!shouldCollapse) return;
80+
setCollapseEligible(prev => {
81+
if (prev[i]) return prev;
82+
return { ...prev, [i]: true };
83+
});
84+
});
85+
}
86+
});
87+
88+
return () => observerRef.current?.disconnect();
89+
}, []);
90+
91+
const setTextRef = useCallback((i: number) => (r: HTMLElement | null) => {
92+
if (r) {
93+
textRefs.current.set(i, r);
94+
// Measure on mount — only set eligible if content is tall enough
95+
if (r.scrollHeight > 64) {
96+
setCollapseEligible(prev => {
97+
if (prev[i]) return prev;
98+
return { ...prev, [i]: true };
99+
});
100+
}
101+
observerRef.current?.observe(r);
102+
} else {
103+
const prev = textRefs.current.get(i);
104+
if (prev) observerRef.current?.unobserve(prev);
105+
textRefs.current.delete(i);
106+
}
107+
}, []);
60108

61109
// Load messages and cypher only preference for current graph on mount
62110
useEffect(() => {
@@ -325,14 +373,7 @@ export default function Chat({ onClose }: Props) {
325373
const i = messages.findIndex(m => m === message);
326374

327375
return (
328-
<div ref={r => {
329-
if (!r) return;
330-
const shouldCollapse = r.scrollHeight > 64;
331-
setCollapseEligible(prev => {
332-
if (prev[i] === shouldCollapse) return prev;
333-
return { ...prev, [i]: shouldCollapse };
334-
});
335-
}} className="flex gap-2 items-start">
376+
<div className="flex gap-2 items-start">
336377
{
337378
collapseEligible[i] &&
338379
<Button
@@ -344,7 +385,7 @@ export default function Chat({ onClose }: Props) {
344385
{queryCollapse[i] ? <ChevronRight size={25} /> : <ChevronDown size={25} />}
345386
</Button>
346387
}
347-
<div className="overflow-hidden SofiaSans">
388+
<div ref={setTextRef(i)} className="overflow-hidden SofiaSans">
348389
{
349390
queryCollapse[i] ? (
350391
<ShadTooltip>
@@ -460,8 +501,8 @@ export default function Chat({ onClose }: Props) {
460501
? <div className="h-8 w-8 rounded-full flex items-center justify-center bg-primary">
461502
<p className="text-foreground text-sm truncate text-center">{message.role.charAt(0).toUpperCase()}</p>
462503
</div>
463-
: <div className="h-8 w-8">
464-
<Image className="rounded-full" src={`/icons/F-${currentTheme}.svg`} style={{ height: "100%", width: "100%" }} alt="Assistant" width={0} height={0} />
504+
: <div className="h-8 w-8 relative">
505+
{mounted && currentTheme && <Image className="rounded-full" src={`/icons/F-${currentTheme}.svg`} alt="Assistant" fill />}
465506
</div>;
466507
return (
467508
<li

app/graph/GraphView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ function GraphView({
183183
/>
184184
{
185185
(labels.length !== 0 || relationships.length !== 0) &&
186-
<div className={cn("w-fit h-1 grow grid gap-2", labels.length !== 0 && relationships.length !== 0 ? "grid-rows-[minmax(0,max-content)_max-content_minmax(0,max-content)]" : "grid-rows-[minmax(0,max-content)]")}>
186+
<div className={cn("w-fit max-w-[200px] h-1 grow grid gap-2", labels.length !== 0 && relationships.length !== 0 ? "grid-rows-[minmax(0,max-content)_max-content_minmax(0,max-content)]" : "grid-rows-[minmax(0,max-content)]")}>
187187
{labels.length !== 0 && <Labels labels={labels} onClick={onLabelClick} label="Labels" type="Graph" />}
188188
{labels.length !== 0 && relationships.length > 0 && <div className="h-px bg-border rounded-full" />}
189189
{relationships.length !== 0 && <Labels labels={relationships} onClick={onRelationshipClick} label="Relationships" type="Graph" />}

app/graph/Selector.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,21 +356,21 @@ export default function Selector<T extends "Graph" | "Schema" = "Graph" | "Schem
356356
if (!historyQuery || !setHistoryQuery) return;
357357

358358
const newQueries = historyQuery.queries.map(q =>
359-
q.text === item.text ? { ...q, fav: !q.fav, name } : q
359+
q.timestamp === item.timestamp ? { ...q, fav: !q.fav, name } : q
360360
);
361361

362362
localStorage.setItem("query history", JSON.stringify(newQueries));
363363

364364
setHistoryQuery(prev => ({
365365
...prev,
366366
queries: newQueries,
367-
currentQuery: prev.currentQuery.text === item.text
367+
currentQuery: prev.currentQuery.timestamp === item.timestamp
368368
? { ...prev.currentQuery, fav: !prev.currentQuery.fav, name }
369369
: prev.currentQuery,
370370
}));
371371

372372
setFilteredQueries(prev =>
373-
prev.map(q => q.text === item.text ? { ...q, fav: !q.fav, name } : q)
373+
prev.map(q => q.timestamp === item.timestamp ? { ...q, fav: !q.fav, name } : q)
374374
);
375375
}, [historyQuery, setHistoryQuery]);
376376

@@ -584,7 +584,9 @@ export default function Selector<T extends "Graph" | "Schema" = "Graph" | "Schem
584584
setHistoryQuery(prev => ({
585585
...prev,
586586
queries: newQueries,
587-
currentQuery: { ...prev.currentQuery, fav: false, name: undefined },
587+
currentQuery: prev.currentQuery.fav
588+
? { ...prev.currentQuery, fav: false, name: undefined }
589+
: prev.currentQuery,
588590
}));
589591
setFilteredQueries(prev => prev.map(q => ({ ...q, fav: false, name: undefined })));
590592
}}

app/graph/graphInfo.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,13 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
102102
const labelColor = label.style.color;
103103

104104
return (
105-
<li key={`${name}-${labelColor}`} className="max-w-full flex gap-1">
105+
<li key={`${name}-${labelColor}`} className="max-w-full flex gap-1 overflow-x-hidden">
106106
<Button
107107
style={{
108108
backgroundColor: labelColor,
109109
color: getContrastTextColor(labelColor)
110110
}}
111-
className="h-6 w-full p-2 rounded-full flex justify-center items-center SofiaSans hover:opacity-80 transition-opacity"
111+
className="w-fit max-w-[calc(100%-24px)] h-6 p-2 rounded-full flex justify-center items-center SofiaSans hover:opacity-80 transition-opacity"
112112
data-testid={`graphInfo${name}Node`}
113113
title={`MATCH (n:${name}) RETURN n
114114
#: ${label.count.toLocaleString()}`}
@@ -222,7 +222,7 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
222222
<li key={key} className="max-w-full">
223223
<Button
224224
title={`MATCH (e) WHERE e.${key} IS NOT NULL RETURN e\nUNION\nMATCH ()-[e]-() WHERE e.${key} IS NOT NULL RETURN e`}
225-
className="h-6 w-full p-2 bg-secondary flex justify-center items-center rounded text-foreground SofiaSans hover:bg-opacity-40 transition-opacity"
225+
className="h-6 w-full p-2 bg-secondary flex justify-center items-center rounded text-foreground SofiaSans hover:opacity-80 transition-opacity"
226226
label={key}
227227
onClick={() => runQuery(
228228
`MATCH (e) WHERE e.${key} IS NOT NULL RETURN e\nUNION\nMATCH ()-[e]-() WHERE e.${key} IS NOT NULL RETURN e`

app/graph/labels.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function Labels<T extends Label | Relationship>({ labels, onClick
1414
const listRef = useRef<HTMLUListElement>(null);
1515

1616
return (
17-
<div className={cn("flex flex-col gap-2 max-w-1/2 bg-background rounded-lg p-1 overflow-hidden")}>
17+
<div className={cn("flex flex-col gap-2 bg-background rounded-lg p-1 overflow-hidden")}>
1818
{
1919
label &&
2020
<h1>{label}</h1>

0 commit comments

Comments
 (0)