Skip to content

Commit 5557362

Browse files
committed
Add Nebula Chat UI
1 parent 6bd9683 commit 5557362

File tree

14 files changed

+1292
-2
lines changed

14 files changed

+1292
-2
lines changed

apps/dashboard/.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,10 @@ NEXT_PUBLIC_TURNSTILE_SITE_KEY=""
9494
TURNSTILE_SECRET_KEY=""
9595
REDIS_URL=""
9696

97-
ANALYTICS_SERVICE_URL=""
97+
ANALYTICS_SERVICE_URL=""
98+
99+
# Nebula Chat
100+
NEBULA_ENDPOINT="" # required
101+
NEBULA_CHAT_ENDPOINT="" # required
102+
NEBULA_SESSION_ENDPOINT="" # required
103+
NEBULA_EXECUTE_ENDPOINT="" # required
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { NextResponse } from "next/server";
2+
import { fetchWithSecretKey } from "../serverUtils";
3+
4+
const NEBULA_CHAT_ENDPOINT = process.env.NEBULA_CHAT_ENDPOINT;
5+
6+
export async function POST(request: Request) {
7+
if (!NEBULA_CHAT_ENDPOINT) {
8+
throw new Error("Chat endpoint is not defined");
9+
}
10+
11+
try {
12+
const body = await request.json();
13+
const { message, user_id, session_id } = body;
14+
const secretKey = request.headers.get("x-thirdweb-key");
15+
16+
if (!message) {
17+
return NextResponse.json(
18+
{ error: "Missing message parameter" },
19+
{ status: 400 },
20+
);
21+
}
22+
if (!secretKey) {
23+
return NextResponse.json(
24+
{ error: "Missing ThirdWeb API key" },
25+
{ status: 401 },
26+
);
27+
}
28+
29+
try {
30+
const data = await fetchWithSecretKey({
31+
endpoint: NEBULA_CHAT_ENDPOINT,
32+
body: { message, user_id, session_id },
33+
secretKey,
34+
timeout: 60000, // 1 minute timeout for chat requests
35+
});
36+
37+
return NextResponse.json(data);
38+
} catch (error: unknown) {
39+
console.error(
40+
"Chat API Error:",
41+
error instanceof Error ? error.stack : error,
42+
);
43+
if (error instanceof Error && error.message.includes("timed out")) {
44+
return NextResponse.json(
45+
{ error: "Request timed out. Please try again." },
46+
{ status: 504 },
47+
);
48+
}
49+
50+
if (error instanceof Error && error.message.includes("401")) {
51+
return NextResponse.json(
52+
{ error: "Invalid Authorization header" },
53+
{ status: 401 },
54+
);
55+
}
56+
return NextResponse.json(
57+
{
58+
error:
59+
error instanceof Error ? error.message : "Failed to fetch from API",
60+
},
61+
{ status: 500 },
62+
);
63+
}
64+
} catch (error) {
65+
console.error(
66+
"Request Error:",
67+
error instanceof Error ? error.stack : error,
68+
);
69+
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
70+
}
71+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { NextResponse } from "next/server";
2+
import { fetchWithSecretKey } from "../serverUtils";
3+
4+
export async function POST(request: Request) {
5+
try {
6+
const body = await request.json();
7+
const { message, user_id, session_id, config } = body;
8+
const secretKey = request.headers.get("x-thirdweb-key");
9+
10+
if (!message) {
11+
return NextResponse.json(
12+
{ error: "Missing message parameter" },
13+
{ status: 400 },
14+
);
15+
}
16+
if (!secretKey) {
17+
return NextResponse.json(
18+
{ error: "Missing ThirdWeb API key" },
19+
{ status: 401 },
20+
);
21+
}
22+
23+
const NEBULA_EXECUTE_ENDPOINT = process.env.NEBULA_EXECUTE_ENDPOINT;
24+
if (!NEBULA_EXECUTE_ENDPOINT) {
25+
throw new Error("Execute endpoint is not defined");
26+
}
27+
28+
try {
29+
const data = await fetchWithSecretKey({
30+
endpoint: NEBULA_EXECUTE_ENDPOINT,
31+
body: { message, user_id, session_id, config },
32+
secretKey,
33+
timeout: 120000, // 2 minute timeout for execute requests
34+
});
35+
36+
return NextResponse.json(data);
37+
} catch (error: unknown) {
38+
console.error("Execute API Error:", error);
39+
if (error instanceof Error && error.message.includes("timed out")) {
40+
return NextResponse.json(
41+
{ error: "Request timed out. Please try again." },
42+
{ status: 504 },
43+
);
44+
}
45+
const errorMessage =
46+
error instanceof Error ? error.message : "Failed to fetch from API";
47+
return NextResponse.json({ error: errorMessage }, { status: 500 });
48+
}
49+
} catch (error) {
50+
console.error("Request Error:", error);
51+
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
52+
}
53+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { NextResponse } from "next/server";
2+
3+
export async function POST(request: Request) {
4+
try {
5+
const body = await request.json();
6+
const { message } = body;
7+
8+
if (!message) {
9+
return NextResponse.json(
10+
{ error: "Missing message parameter" },
11+
{ status: 400 },
12+
);
13+
}
14+
15+
const NEBULA_CHAT_ENDPOINT = process.env.NEBULA_CHAT_ENDPOINT;
16+
const DASHBOARD_SECRET_KEY = process.env.DASHBOARD_SECRET_KEY;
17+
18+
try {
19+
if (!NEBULA_CHAT_ENDPOINT) {
20+
throw new Error("Chat endpoint is not defined");
21+
}
22+
if (!DASHBOARD_SECRET_KEY) {
23+
throw new Error("ThirdWeb secret key is not defined");
24+
}
25+
26+
const response = await fetch(NEBULA_CHAT_ENDPOINT, {
27+
method: "POST",
28+
headers: {
29+
"Content-Type": "application/json",
30+
Accept: "application/json",
31+
"x-secret-key": DASHBOARD_SECRET_KEY,
32+
},
33+
body: JSON.stringify({ message }),
34+
});
35+
36+
if (!response.ok) {
37+
throw new Error(`HTTP error! status: ${response.status}`);
38+
}
39+
40+
const data = await response.json();
41+
return NextResponse.json(data);
42+
} catch (error) {
43+
console.error("Proxy error:", error);
44+
return NextResponse.json(
45+
{ error: "Failed to fetch from API" },
46+
{ status: 500 },
47+
);
48+
}
49+
} catch (error) {
50+
console.error("API Error:", error);
51+
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
52+
}
53+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
interface FetchWithKeyOptions {
2+
endpoint: string;
3+
body: Record<string, unknown>;
4+
secretKey: string;
5+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
6+
timeout?: number;
7+
}
8+
9+
export async function fetchWithSecretKey({
10+
endpoint,
11+
body,
12+
secretKey,
13+
method = 'POST',
14+
timeout = 30000 // 30 second default timeout
15+
}: FetchWithKeyOptions) {
16+
console.log('Sending request to:', endpoint);
17+
console.log('Request method:', method);
18+
console.log('Request body:', body);
19+
20+
const controller = new AbortController();
21+
const timeoutId = setTimeout(() => controller.abort(), timeout);
22+
23+
try {
24+
const response = await fetch(endpoint, {
25+
method,
26+
headers: {
27+
'Content-Type': 'application/json',
28+
'Accept': 'application/json',
29+
'x-secret-key': secretKey,
30+
},
31+
body: JSON.stringify(body),
32+
signal: controller.signal
33+
});
34+
35+
if (!response.ok) {
36+
if (response.status === 504) {
37+
throw new Error('Request timed out. Please try again.');
38+
}
39+
throw new Error(`HTTP error! status: ${response.status}`);
40+
}
41+
42+
const responseData = await response.json();
43+
console.log('Response data:', responseData);
44+
return responseData;
45+
} catch (error: unknown) {
46+
if (error instanceof Error && error.name === 'AbortError') {
47+
throw new Error('Request timed out. Please try again.');
48+
}
49+
throw error;
50+
} finally {
51+
clearTimeout(timeoutId);
52+
}
53+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { NextResponse } from "next/server";
2+
import { fetchWithSecretKey } from "../serverUtils";
3+
4+
const NEBULA_SESSION_ENDPOINT = process.env.NEBULA_SESSION_ENDPOINT;
5+
6+
export async function POST(request: Request) {
7+
try {
8+
const body = await request.json();
9+
const { session_id, config = {}, action } = body;
10+
const secretKey = request.headers.get("x-thirdweb-key");
11+
12+
if (!secretKey) {
13+
return NextResponse.json(
14+
{ error: "Missing ThirdWeb API key" },
15+
{ status: 401 },
16+
);
17+
}
18+
19+
if (!NEBULA_SESSION_ENDPOINT) {
20+
throw new Error("Session endpoint is not defined");
21+
}
22+
23+
if (action === "create_session") {
24+
const requestBody = {
25+
can_execute: !!config,
26+
config,
27+
};
28+
29+
const data = await fetchWithSecretKey({
30+
endpoint: NEBULA_SESSION_ENDPOINT,
31+
body: requestBody,
32+
secretKey,
33+
});
34+
35+
return NextResponse.json(data);
36+
}
37+
38+
if (action === "update_session") {
39+
if (!session_id) {
40+
return NextResponse.json(
41+
{ error: "Missing session_id for update" },
42+
{ status: 400 },
43+
);
44+
}
45+
46+
const requestBody = {
47+
can_execute: !!config,
48+
config,
49+
};
50+
51+
const data = await fetchWithSecretKey({
52+
endpoint: `${NEBULA_SESSION_ENDPOINT}/${session_id}`,
53+
method: "PUT",
54+
body: requestBody,
55+
secretKey,
56+
});
57+
58+
return NextResponse.json(data);
59+
}
60+
61+
return NextResponse.json({ error: "Invalid action" }, { status: 400 });
62+
} catch (error) {
63+
console.error("Session API Error:", error);
64+
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
65+
}
66+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export async function validateEndpoints() {
2+
const NEBULA_CHAT_ENDPOINT = process.env.NEBULA_CHAT_ENDPOINT;
3+
const NEBULA_EXECUTE_ENDPOINT = process.env.NEBULA_EXECUTE_ENDPOINT;
4+
const NEBULA_SESSION_ENDPOINT = process.env.NEBULA_SESSION_ENDPOINT;
5+
const DASHBOARD_SECRET_KEY = process.env.DASHBOARD_SECRET_KEY;
6+
7+
if (
8+
!NEBULA_CHAT_ENDPOINT ||
9+
!NEBULA_EXECUTE_ENDPOINT ||
10+
!NEBULA_SESSION_ENDPOINT
11+
) {
12+
throw new Error("Endpoints are not defined");
13+
}
14+
if (!DASHBOARD_SECRET_KEY) {
15+
throw new Error("ThirdWeb secret key is not defined");
16+
}
17+
18+
return {
19+
NEBULA_CHAT_ENDPOINT,
20+
NEBULA_EXECUTE_ENDPOINT,
21+
NEBULA_SESSION_ENDPOINT,
22+
DASHBOARD_SECRET_KEY,
23+
};
24+
}

0 commit comments

Comments
 (0)