Skip to content

Commit 7b94e0f

Browse files
thirdweb AI inside dashboard
1 parent e37bd8e commit 7b94e0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+6494
-99
lines changed

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"compare-versions": "^6.1.0",
3636
"date-fns": "4.1.0",
3737
"fast-xml-parser": "^5.2.5",
38+
"fetch-event-stream": "0.1.5",
3839
"fuse.js": "7.1.0",
3940
"input-otp": "^1.4.1",
4041
"ioredis": "^5.6.1",

apps/dashboard/src/@/actions/proxies.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { getAuthToken } from "@/api/auth-token";
44
import {
55
NEXT_PUBLIC_ENGINE_CLOUD_URL,
6+
NEXT_PUBLIC_THIRDWEB_AI_HOST,
67
NEXT_PUBLIC_THIRDWEB_API_HOST,
78
} from "@/constants/public-envs";
89
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
@@ -14,6 +15,7 @@ type ProxyActionParams = {
1415
body?: string;
1516
headers?: Record<string, string>;
1617
parseAsText?: boolean;
18+
signal?: AbortSignal;
1719
};
1820

1921
type ProxyActionResult<T> =

apps/dashboard/src/@/components/blocks/full-width-sidebar-layout.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
44
import { usePathname } from "next/navigation";
55
import { useMemo } from "react";
6-
import { AppFooter } from "@/components/footers/app-footer";
76
import {
87
Collapsible,
98
CollapsibleContent,
@@ -91,7 +90,6 @@ export function FullWidthSidebarLayout(props: {
9190
<main className="flex min-w-0 grow flex-col max-sm:w-full">
9291
{children}
9392
</main>
94-
<AppFooter />
9593
</div>
9694
</div>
9795
);

apps/dashboard/src/@/components/chat/ChatBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function ChatBar(props: {
8787
<PopoverContent className="w-72">
8888
<div>
8989
<p className="mb-3 text-muted-foreground text-sm">
90-
Get access to image uploads by signing in to Nebula
90+
Get access to image uploads by signing in to thirdweb
9191
</p>
9292
<Button
9393
className="w-full"

apps/dashboard/src/@/components/chat/CustomChatButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export function CustomChatButton(props: {
2828
const closeModal = useCallback(() => setIsOpen(false), []);
2929
const ref = useRef<HTMLDivElement>(null);
3030

31-
if (layoutSegments[0] === "~" && layoutSegments[1] === "support") {
31+
if (
32+
(layoutSegments[0] === "~" && layoutSegments[1] === "support") ||
33+
layoutSegments.includes("ai")
34+
) {
3235
return null;
3336
}
3437

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import type React from "react";
4+
import { useRef } from "react";
5+
import { Button } from "@/components/ui/button";
6+
7+
interface ImageUploadProps {
8+
value: File | undefined;
9+
onChange?: (files: File[]) => void;
10+
children?: React.ReactNode;
11+
variant?: React.ComponentProps<typeof Button>["variant"];
12+
className?: string;
13+
multiple?: boolean;
14+
accept: string;
15+
}
16+
17+
export function ImageUploadButton(props: ImageUploadProps) {
18+
const fileInputRef = useRef<HTMLInputElement>(null);
19+
20+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
21+
const files = Array.from(e.target.files || []);
22+
props.onChange?.(files);
23+
};
24+
25+
return (
26+
<div>
27+
<Button
28+
className={props.className}
29+
onClick={() => fileInputRef.current?.click()}
30+
variant={props.variant}
31+
>
32+
{props.children}
33+
</Button>
34+
<input
35+
accept={props.accept}
36+
aria-label="Upload image"
37+
className="hidden"
38+
multiple={props.multiple}
39+
onChange={handleFileChange}
40+
ref={fileInputRef}
41+
type="file"
42+
/>
43+
</div>
44+
);
45+
}

apps/dashboard/src/@/constants/public-envs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ export const NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET =
2929

3030
export const NEXT_PUBLIC_DEMO_ENGINE_URL =
3131
process.env.NEXT_PUBLIC_DEMO_ENGINE_URL || "";
32+
33+
export const NEXT_PUBLIC_THIRDWEB_AI_HOST =
34+
process.env.NEXT_PUBLIC_THIRDWEB_AI_HOST || "https://nebula-api.thirdweb.com";

apps/dashboard/src/@/storybook/stubs.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,32 @@ export function newAccountStub(overrides?: Partial<Account>): Account {
279279
...overrides,
280280
};
281281
}
282+
283+
export function randomLorem(length: number) {
284+
const loremWords = [
285+
"lorem",
286+
"ipsum",
287+
"dolor",
288+
"sit",
289+
"amet",
290+
"consectetur",
291+
"adipiscing",
292+
"elit",
293+
"sed",
294+
"do",
295+
"eiusmod",
296+
"tempor",
297+
"incididunt",
298+
"ut",
299+
"labore",
300+
"et",
301+
"dolore",
302+
"magna",
303+
"aliqua",
304+
];
305+
306+
return Array.from({ length }, () => {
307+
const randomIndex = Math.floor(Math.random() * loremWords.length);
308+
return loremWords[randomIndex];
309+
}).join(" ");
310+
}

apps/dashboard/src/app/(app)/(dashboard)/(chain)/components/server/products.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ export const products = [
6262
description: "The most powerful AI for interacting with the blockchain",
6363
icon: NebulaIcon,
6464
id: "nebula",
65-
link: "https://thirdweb.com/nebula",
66-
name: "Nebula",
65+
link: "https://thirdweb.com/ai",
66+
name: "thirdweb AI",
6767
},
6868
] satisfies Array<{
6969
name: string;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { redirect } from "next/navigation";
2+
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
3+
import { getAuthToken } from "@/api/auth-token";
4+
import { getProject } from "@/api/project/projects";
5+
import type { DurationId } from "@/components/analytics/date-range-selector";
6+
import { ResponsiveTimeFilters } from "@/components/analytics/responsive-time-filters";
7+
import { ProjectPage } from "@/components/blocks/project-page/project-page";
8+
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
9+
import { NebulaIcon } from "@/icons/NebulaIcon";
10+
import { getFiltersFromSearchParams } from "@/lib/time";
11+
import { loginRedirect } from "@/utils/redirects";
12+
import { AiAnalytics } from "./chart";
13+
import { AiSummary } from "./chart/Summary";
14+
15+
export default async function Page(props: {
16+
params: Promise<{ team_slug: string; project_slug: string }>;
17+
searchParams: Promise<{
18+
from?: string;
19+
to?: string;
20+
type?: string;
21+
interval?: string;
22+
}>;
23+
}) {
24+
const [searchParams, params] = await Promise.all([
25+
props.searchParams,
26+
props.params,
27+
]);
28+
29+
const { team_slug, project_slug } = params;
30+
31+
const [project, authToken] = await Promise.all([
32+
getProject(team_slug, project_slug),
33+
getAuthToken(),
34+
]);
35+
36+
if (!authToken) {
37+
loginRedirect(`/team/${team_slug}/${project_slug}/ai`);
38+
}
39+
40+
if (!project) {
41+
redirect(`/team/${team_slug}`);
42+
}
43+
44+
const client = getClientThirdwebClient({
45+
jwt: authToken,
46+
teamId: project.teamId,
47+
});
48+
49+
const defaultRange = "last-30" as DurationId;
50+
const { range, interval } = getFiltersFromSearchParams({
51+
defaultRange,
52+
from: searchParams.from,
53+
interval: searchParams.interval,
54+
to: searchParams.to,
55+
});
56+
57+
return (
58+
<ResponsiveSearchParamsProvider value={searchParams}>
59+
<ProjectPage
60+
header={{
61+
icon: NebulaIcon,
62+
client,
63+
title: "AI Analytics",
64+
description: "Track your AI app usage and performance",
65+
actions: null,
66+
links: [
67+
{
68+
href: "https://portal.thirdweb.com/ai/chat",
69+
type: "docs",
70+
},
71+
{
72+
href: "https://api.thirdweb.com/reference#tag/ai/post/ai/chat",
73+
type: "api",
74+
},
75+
{
76+
href: "https://playground.thirdweb.com/ai/chat",
77+
type: "playground",
78+
},
79+
],
80+
}}
81+
>
82+
<div className="flex flex-col gap-4 md:gap-6">
83+
<ResponsiveTimeFilters defaultRange={defaultRange} />
84+
<AiSummary
85+
projectId={project.id}
86+
teamId={project.teamId}
87+
authToken={authToken}
88+
range={range}
89+
/>
90+
91+
<AiAnalytics
92+
interval={interval}
93+
projectId={project.id}
94+
range={range}
95+
teamId={project.teamId}
96+
authToken={authToken}
97+
/>
98+
</div>
99+
</ProjectPage>
100+
</ResponsiveSearchParamsProvider>
101+
);
102+
}

0 commit comments

Comments
 (0)