Skip to content

Commit a35abab

Browse files
authored
Merge pull request #1300 from FalkorDB/staging
Staging
2 parents 5d9ea64 + 2cbf240 commit a35abab

File tree

12 files changed

+152
-106
lines changed

12 files changed

+152
-106
lines changed

app/api/auth/DBVersion/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ export async function GET() {
1313
const { client } = session;
1414

1515
try {
16+
// result: [module name, module version]
1617
const result = await (await client.connection).moduleList();
17-
return NextResponse.json({ result: [result[1][1], result[1][3]] }, { status: 200 });
18+
19+
return NextResponse.json({ result: [result[0][1], result[0][3]] }, { status: 200 });
1820
} catch (error) {
1921
console.error(error);
2022
return NextResponse.json(
2123
{ message: (error as Error).message },
22-
{ status: (error as Error).message.includes("NOPERM") ? 200 : 400 }
24+
{ status: 400 }
2325
);
2426
}
2527
} catch (err) {

app/api/auth/[...nextauth]/options.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ async function tryJWTAuthentication(): Promise<{ client: FalkorDB; user: Authent
251251
try {
252252
const connection = await client.connection;
253253
await connection.ping();
254-
254+
255255
// Connection is healthy, reuse it
256256
const user = createUserFromJWTPayload(payload);
257257
return { client, user };
@@ -260,13 +260,13 @@ async function tryJWTAuthentication(): Promise<{ client: FalkorDB; user: Authent
260260
// eslint-disable-next-line no-console
261261
console.warn("Connection health check failed, recreating:", pingError);
262262
connections.delete(payload.sub);
263-
263+
264264
try {
265265
await client.close();
266266
} catch (closeError) {
267267
// Ignore close errors on dead connections
268268
}
269-
269+
270270
client = undefined; // Will be recreated below
271271
}
272272
}
@@ -277,7 +277,7 @@ async function tryJWTAuthentication(): Promise<{ client: FalkorDB; user: Authent
277277
// Fetch password from Token DB (6380) - NOT from JWT
278278
const { getPasswordFromTokenDB } = await import('../tokenUtils');
279279
const password = await getPasswordFromTokenDB(payload.jti);
280-
280+
281281
// Create new connection with retrieved password
282282
const { client: reconnectedClient } = await newClient(
283283
{
@@ -290,7 +290,7 @@ async function tryJWTAuthentication(): Promise<{ client: FalkorDB; user: Authent
290290
},
291291
payload.sub
292292
);
293-
293+
294294
client = reconnectedClient;
295295
// Connection is already cached in connections Map by newClient()
296296
} catch (connectionError) {
@@ -370,6 +370,7 @@ const authOptions: AuthOptions = {
370370
tls: user.tls,
371371
ca: user.ca,
372372
role: user.role,
373+
url: user.url,
373374
};
374375
}
375376

@@ -392,6 +393,7 @@ const authOptions: AuthOptions = {
392393
tls: token.tls as boolean,
393394
ca: token.ca,
394395
role: token.role as Role,
396+
url: token.url as string | undefined,
395397
},
396398
};
397399
}
@@ -463,6 +465,7 @@ export async function getClient() {
463465
password: user.password,
464466
tls: String(user.tls),
465467
ca: user.ca,
468+
url: user.url,
466469
},
467470
user.id
468471
);

app/api/chat/route.tsx

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ export async function GET() {
2929
return NextResponse.json(data)
3030
} catch (error) {
3131
const { message } = (error as Error)
32-
33-
if (message.includes("fetch failed")) {
34-
return NextResponse.json({ message: "Server is not available" }, { status: 200 })
32+
33+
// Gracefully handle missing endpoint or server unavailability
34+
// Return empty object to allow chat to be displayed
35+
if (message.includes("fetch failed") || message.includes("Not Found") || message.includes("NOT_FOUND") || message.includes("could not be found")) {
36+
return NextResponse.json({ message }, { status: 200 })
3537
}
36-
38+
3739
console.error(error)
3840
return NextResponse.json({ error: message }, { status: 400 })
3941
}
@@ -52,6 +54,7 @@ export async function POST(request: NextRequest) {
5254
const writer = writable.getWriter()
5355

5456
try {
57+
// Verify authentication via getClient
5558
const session = await getClient()
5659

5760
if (session instanceof NextResponse) {
@@ -62,10 +65,11 @@ export async function POST(request: NextRequest) {
6265

6366
// Validate request body
6467
const validation = validateBody(chatRequest, body);
65-
68+
6669
if (!validation.success) {
6770
writer.write(encoder.encode(`event: error status: ${400} data: ${JSON.stringify(validation.error)}\n\n`))
6871
writer.close()
72+
6973
return new Response(readable, {
7074
headers: {
7175
"Content-Type": "text/event-stream",
@@ -78,14 +82,15 @@ export async function POST(request: NextRequest) {
7882
const { messages, graphName, key, model } = validation.data
7983

8084
try {
81-
82-
const requestBody = {
83-
"chat_request": {
84-
messages,
85-
},
85+
const requestBody: Record<string, unknown> = {
86+
chat_request: { messages },
8687
"graph_name": graphName,
87-
key,
88-
model,
88+
"model": model || "gpt-4o-mini",
89+
}
90+
91+
// Only add key if provided
92+
if (key) {
93+
requestBody.key = key
8994
}
9095

9196
const response = await fetch(`${CHAT_URL}text_to_cypher`, {
@@ -100,38 +105,47 @@ export async function POST(request: NextRequest) {
100105
throw new Error(`Error: ${await response.text()}`);
101106
}
102107

108+
// Handle streaming SSE response from text-to-cypher
103109
const reader = response.body?.getReader();
104110
const decoder = new TextDecoder();
105111

106112
const processStream = async () => {
107113
if (!reader) return;
108114

109115
const { done, value } = await reader.read();
110-
if (done) return;
116+
if (done) {
117+
writer.close();
118+
return;
119+
}
111120

112121
const chunk = decoder.decode(value, { stream: true });
113-
114122
const lines = chunk.split('\n').filter(line => line);
115-
let isResult = false
116-
123+
let isResult = false;
117124

118125
lines.forEach(line => {
119-
const data = JSON.parse(line.split("data:")[1])
120-
const type: EventType = Object.keys(data)[0] as EventType
126+
if (line.startsWith('data: ')) {
127+
try {
128+
const data = JSON.parse(line.slice(6)); // Remove 'data: ' prefix
129+
const type: EventType = Object.keys(data)[0] as EventType
121130

122-
isResult = type === "Result" || type === "Error"
131+
isResult = type === "Result" || type === "Error"
123132

124-
writer.write(encoder.encode(`event: ${type} data: ${data[type]}\n\n`))
125-
})
133+
writer.write(encoder.encode(`event: ${type} data: ${data[type]}\n\n`))
134+
} catch (parseError) {
135+
console.error("Failed to parse SSE data:", line, parseError)
136+
}
137+
}
138+
});
126139

140+
// Continue processing the stream unless we received Result or Error
127141
if (!isResult) {
128-
processStream();
142+
await processStream();
129143
} else {
130144
writer.close();
131145
}
132146
};
133147

134-
processStream()
148+
await processStream();
135149
} catch (error) {
136150
console.error(error)
137151
writer.write(encoder.encode(`event: error status: ${400} data: ${JSON.stringify((error as Error).message)}\n\n`))

app/api/graph/[graph]/memory/route.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,37 @@ export async function GET(
2727
return session;
2828
}
2929

30-
const res = await getDBVersion();
30+
try {
31+
const res = await getDBVersion();
3132

32-
if (!res.ok) {
33-
return NextResponse.json(
34-
{
35-
message: `Failed to retrieve database version: ${await res.text()}`,
36-
},
37-
{ status: 400 }
38-
);
39-
}
33+
if (!res.ok) {
34+
const err = await res.text();
4035

41-
const [name, version] = (await res.json()).result || ["", 0];
36+
let message;
4237

43-
if (name !== "graph" || version < MEMORY_USAGE_VERSION_THRESHOLD) {
44-
return NextResponse.json(
45-
{
46-
message: `Memory usage feature requires graph module version ${MEMORY_USAGE_VERSION_THRESHOLD} or higher. Current version: ${version}`,
47-
},
48-
{ status: 400 }
49-
);
50-
}
38+
try {
39+
message = JSON.parse(err).message;
40+
} catch {
41+
message = err;
42+
}
5143

52-
const { client } = session;
53-
const { graph } = await params;
44+
return NextResponse.json(
45+
{ message: `Failed to retrieve database version: ${message}` },
46+
{ status: 400 }
47+
);
48+
}
5449

55-
try {
50+
const [name, version] = (await res.json()).result;
51+
52+
if (name !== "graph" || version < MEMORY_USAGE_VERSION_THRESHOLD) {
53+
return NextResponse.json(
54+
{ message: `Memory usage feature requires graph module version ${MEMORY_USAGE_VERSION_THRESHOLD} or higher. Current version: ${version}` },
55+
{ status: 400 }
56+
);
57+
}
58+
59+
const { client } = session;
60+
const { graph } = await params;
5661
const result = await client.selectGraph(graph).memoryUsage();
5762

5863
return NextResponse.json({ result }, { status: 200 });

app/graph/Chat.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ export default function Chat({ onClose }: Props) {
178178

179179
case "Result":
180180
isResult = true
181-
182181
break;
183182

184183
case "Error":

app/providers.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,10 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
457457
if (result.ok) {
458458
const json = await result.json()
459459

460-
if (!json.message) setDisplayChat(true)
460+
if (json.message) return
461+
462+
setDisplayChat(true)
463+
461464
if (!json.model && json.error) {
462465
setNavigateToSettings(true)
463466
}
@@ -470,7 +473,7 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
470473
securedFetch("/api/status", {
471474
method: "GET",
472475
}, toast, setIndicator)
473-
}, [])
476+
}, [toast])
474477

475478
useEffect(() => {
476479
let interval: NodeJS.Timeout | undefined

app/settings/browserSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Button from "../components/ui/Button";
1313
import Input from "../components/ui/Input";
1414
import Combobox from "../components/ui/combobox";
1515

16-
const MODELS = ["gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1"]
16+
const MODELS = ["gpt-4o-mini", "gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"]
1717

1818
export default function BrowserSettings() {
1919
const {

lib/utils.ts

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ export type Message = {
6464
role: "user" | "assistant";
6565
content: string;
6666
type:
67-
| "Text"
68-
| "Result"
69-
| "Error"
70-
| "Status"
71-
| "CypherQuery"
72-
| "CypherResult"
73-
| "Schema";
67+
| "Text"
68+
| "Result"
69+
| "Error"
70+
| "Status"
71+
| "CypherQuery"
72+
| "CypherResult"
73+
| "Schema";
7474
};
7575

7676
export type Cell = SelectCell | TextCell | ObjectCell | ReadOnlyCell | LazyCell;
@@ -145,12 +145,20 @@ export async function securedFetch(
145145
const response = await fetch(input, init);
146146
const { status } = response;
147147
if (status >= 300) {
148-
const err = await response.text();
148+
let message = await response.text();
149+
150+
try {
151+
message = JSON.parse(message).message;
152+
} catch {
153+
// message is already text
154+
}
155+
149156
toast({
150157
title: "Error",
151-
description: err,
158+
description: message,
152159
variant: "destructive",
153160
});
161+
154162
if (status === 401 || status >= 500) {
155163
setIndicator("offline");
156164
}
@@ -334,30 +342,30 @@ export const getNodeDisplayText = (node: Node, displayTextPriority: TextPriority
334342
const { data: nodeData } = node;
335343

336344
const displayText = displayTextPriority.find(({ name, ignore }) => {
337-
const key = ignore
338-
? Object.keys(nodeData).find(
339-
(k) => k.toLowerCase() === name.toLowerCase()
340-
)
341-
: name;
342-
343-
return (
344-
key &&
345-
nodeData[key] &&
346-
typeof nodeData[key] === "string" &&
347-
nodeData[key].trim().length > 0
348-
);
345+
const key = ignore
346+
? Object.keys(nodeData).find(
347+
(k) => k.toLowerCase() === name.toLowerCase()
348+
)
349+
: name;
350+
351+
return (
352+
key &&
353+
nodeData[key] &&
354+
typeof nodeData[key] === "string" &&
355+
nodeData[key].trim().length > 0
356+
);
349357
});
350358

351359
if (displayText) {
352-
const key = displayText.ignore
353-
? Object.keys(nodeData).find(
354-
(k) => k.toLowerCase() === displayText.name.toLowerCase()
355-
)
356-
: displayText.name;
357-
358-
if (key) {
359-
return String(nodeData[key]);
360-
}
360+
const key = displayText.ignore
361+
? Object.keys(nodeData).find(
362+
(k) => k.toLowerCase() === displayText.name.toLowerCase()
363+
)
364+
: displayText.name;
365+
366+
if (key) {
367+
return String(nodeData[key]);
368+
}
361369
}
362370

363371
return String(node.id);

0 commit comments

Comments
 (0)