create basic hello world screenshot

This commit is contained in:
Nate Kelley 2025-10-02 13:39:24 -06:00
parent 1c76851c2e
commit 0b265b6a9d
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 138 additions and 3 deletions

View File

@ -0,0 +1,5 @@
import { z } from 'zod';
export const GetScreenshotRequestSharedSchema = z.object({});
export type GetScreenshotRequestShared = z.infer<typeof GetScreenshotRequestSharedSchema>;

View File

@ -18,6 +18,7 @@ import { Route as AuthRouteImport } from './routes/auth'
import { Route as AppRouteImport } from './routes/app' import { Route as AppRouteImport } from './routes/app'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as AppIndexRouteImport } from './routes/app/index' import { Route as AppIndexRouteImport } from './routes/app/index'
import { Route as ScreenshotsHelloWorldRouteImport } from './routes/screenshots/hello-world'
import { Route as InfoGettingStartedRouteImport } from './routes/info/getting-started' import { Route as InfoGettingStartedRouteImport } from './routes/info/getting-started'
import { Route as AuthResetPasswordRouteImport } from './routes/auth.reset-password' import { Route as AuthResetPasswordRouteImport } from './routes/auth.reset-password'
import { Route as AuthLogoutRouteImport } from './routes/auth.logout' import { Route as AuthLogoutRouteImport } from './routes/auth.logout'
@ -154,6 +155,7 @@ import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdCont
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentChartRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/chart' import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentChartRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/chart'
import { ServerRoute as AuthConfirmServerRouteImport } from './routes/auth.confirm' import { ServerRoute as AuthConfirmServerRouteImport } from './routes/auth.confirm'
import { ServerRoute as AuthCallbackServerRouteImport } from './routes/auth.callback' import { ServerRoute as AuthCallbackServerRouteImport } from './routes/auth.callback'
import { ServerRoute as ScreenshotsMetricsMetricIdServerRouteImport } from './routes/screenshots/metrics.$metricId'
const EmbedChatChatIdReportsReportIdRouteImport = createFileRoute( const EmbedChatChatIdReportsReportIdRouteImport = createFileRoute(
'/embed/chat/$chatId/reports/$reportId', '/embed/chat/$chatId/reports/$reportId',
@ -235,6 +237,11 @@ const AppIndexRoute = AppIndexRouteImport.update({
path: '/', path: '/',
getParentRoute: () => AppRoute, getParentRoute: () => AppRoute,
} as any) } as any)
const ScreenshotsHelloWorldRoute = ScreenshotsHelloWorldRouteImport.update({
id: '/screenshots/hello-world',
path: '/screenshots/hello-world',
getParentRoute: () => rootRouteImport,
} as any)
const InfoGettingStartedRoute = InfoGettingStartedRouteImport.update({ const InfoGettingStartedRoute = InfoGettingStartedRouteImport.update({
id: '/info/getting-started', id: '/info/getting-started',
path: '/info/getting-started', path: '/info/getting-started',
@ -1180,6 +1187,12 @@ const AuthCallbackServerRoute = AuthCallbackServerRouteImport.update({
path: '/auth/callback', path: '/auth/callback',
getParentRoute: () => rootServerRouteImport, getParentRoute: () => rootServerRouteImport,
} as any) } as any)
const ScreenshotsMetricsMetricIdServerRoute =
ScreenshotsMetricsMetricIdServerRouteImport.update({
id: '/screenshots/metrics/$metricId',
path: '/screenshots/metrics/$metricId',
getParentRoute: () => rootServerRouteImport,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
@ -1191,6 +1204,7 @@ export interface FileRoutesByFullPath {
'/auth/logout': typeof AuthLogoutRoute '/auth/logout': typeof AuthLogoutRoute
'/auth/reset-password': typeof AuthResetPasswordRoute '/auth/reset-password': typeof AuthResetPasswordRoute
'/info/getting-started': typeof InfoGettingStartedRoute '/info/getting-started': typeof InfoGettingStartedRoute
'/screenshots/hello-world': typeof ScreenshotsHelloWorldRoute
'/app/': typeof AppIndexRoute '/app/': typeof AppIndexRoute
'/app/home': typeof AppAppHomeRouteWithChildren '/app/home': typeof AppAppHomeRouteWithChildren
'/app/new-user': typeof AppAppNewUserRouteWithChildren '/app/new-user': typeof AppAppNewUserRouteWithChildren
@ -1327,6 +1341,7 @@ export interface FileRoutesByTo {
'/auth/logout': typeof AuthLogoutRoute '/auth/logout': typeof AuthLogoutRoute
'/auth/reset-password': typeof AuthResetPasswordRoute '/auth/reset-password': typeof AuthResetPasswordRoute
'/info/getting-started': typeof InfoGettingStartedRoute '/info/getting-started': typeof InfoGettingStartedRoute
'/screenshots/hello-world': typeof ScreenshotsHelloWorldRoute
'/app/test-pagination': typeof AppAppTestPaginationRoute '/app/test-pagination': typeof AppAppTestPaginationRoute
'/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute '/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute
'/embed/metric/$metricId': typeof EmbedMetricMetricIdRoute '/embed/metric/$metricId': typeof EmbedMetricMetricIdRoute
@ -1440,6 +1455,7 @@ export interface FileRoutesById {
'/auth/logout': typeof AuthLogoutRoute '/auth/logout': typeof AuthLogoutRoute
'/auth/reset-password': typeof AuthResetPasswordRoute '/auth/reset-password': typeof AuthResetPasswordRoute
'/info/getting-started': typeof InfoGettingStartedRoute '/info/getting-started': typeof InfoGettingStartedRoute
'/screenshots/hello-world': typeof ScreenshotsHelloWorldRoute
'/app/': typeof AppIndexRoute '/app/': typeof AppIndexRoute
'/app/_app/_asset': typeof AppAppAssetRouteWithChildren '/app/_app/_asset': typeof AppAppAssetRouteWithChildren
'/app/_app/home': typeof AppAppHomeRouteWithChildren '/app/_app/home': typeof AppAppHomeRouteWithChildren
@ -1597,6 +1613,7 @@ export interface FileRouteTypes {
| '/auth/logout' | '/auth/logout'
| '/auth/reset-password' | '/auth/reset-password'
| '/info/getting-started' | '/info/getting-started'
| '/screenshots/hello-world'
| '/app/' | '/app/'
| '/app/home' | '/app/home'
| '/app/new-user' | '/app/new-user'
@ -1733,6 +1750,7 @@ export interface FileRouteTypes {
| '/auth/logout' | '/auth/logout'
| '/auth/reset-password' | '/auth/reset-password'
| '/info/getting-started' | '/info/getting-started'
| '/screenshots/hello-world'
| '/app/test-pagination' | '/app/test-pagination'
| '/embed/dashboard/$dashboardId' | '/embed/dashboard/$dashboardId'
| '/embed/metric/$metricId' | '/embed/metric/$metricId'
@ -1845,6 +1863,7 @@ export interface FileRouteTypes {
| '/auth/logout' | '/auth/logout'
| '/auth/reset-password' | '/auth/reset-password'
| '/info/getting-started' | '/info/getting-started'
| '/screenshots/hello-world'
| '/app/' | '/app/'
| '/app/_app/_asset' | '/app/_app/_asset'
| '/app/_app/home' | '/app/_app/home'
@ -1998,31 +2017,43 @@ export interface RootRouteChildren {
EmbedRoute: typeof EmbedRouteWithChildren EmbedRoute: typeof EmbedRouteWithChildren
HealthcheckRoute: typeof HealthcheckRoute HealthcheckRoute: typeof HealthcheckRoute
InfoGettingStartedRoute: typeof InfoGettingStartedRoute InfoGettingStartedRoute: typeof InfoGettingStartedRoute
ScreenshotsHelloWorldRoute: typeof ScreenshotsHelloWorldRoute
} }
export interface FileServerRoutesByFullPath { export interface FileServerRoutesByFullPath {
'/auth/callback': typeof AuthCallbackServerRoute '/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute '/auth/confirm': typeof AuthConfirmServerRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdServerRoute
} }
export interface FileServerRoutesByTo { export interface FileServerRoutesByTo {
'/auth/callback': typeof AuthCallbackServerRoute '/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute '/auth/confirm': typeof AuthConfirmServerRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdServerRoute
} }
export interface FileServerRoutesById { export interface FileServerRoutesById {
__root__: typeof rootServerRouteImport __root__: typeof rootServerRouteImport
'/auth/callback': typeof AuthCallbackServerRoute '/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute '/auth/confirm': typeof AuthConfirmServerRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdServerRoute
} }
export interface FileServerRouteTypes { export interface FileServerRouteTypes {
fileServerRoutesByFullPath: FileServerRoutesByFullPath fileServerRoutesByFullPath: FileServerRoutesByFullPath
fullPaths: '/auth/callback' | '/auth/confirm' fullPaths:
| '/auth/callback'
| '/auth/confirm'
| '/screenshots/metrics/$metricId'
fileServerRoutesByTo: FileServerRoutesByTo fileServerRoutesByTo: FileServerRoutesByTo
to: '/auth/callback' | '/auth/confirm' to: '/auth/callback' | '/auth/confirm' | '/screenshots/metrics/$metricId'
id: '__root__' | '/auth/callback' | '/auth/confirm' id:
| '__root__'
| '/auth/callback'
| '/auth/confirm'
| '/screenshots/metrics/$metricId'
fileServerRoutesById: FileServerRoutesById fileServerRoutesById: FileServerRoutesById
} }
export interface RootServerRouteChildren { export interface RootServerRouteChildren {
AuthCallbackServerRoute: typeof AuthCallbackServerRoute AuthCallbackServerRoute: typeof AuthCallbackServerRoute
AuthConfirmServerRoute: typeof AuthConfirmServerRoute AuthConfirmServerRoute: typeof AuthConfirmServerRoute
ScreenshotsMetricsMetricIdServerRoute: typeof ScreenshotsMetricsMetricIdServerRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@ -2069,6 +2100,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppIndexRouteImport preLoaderRoute: typeof AppIndexRouteImport
parentRoute: typeof AppRoute parentRoute: typeof AppRoute
} }
'/screenshots/hello-world': {
id: '/screenshots/hello-world'
path: '/screenshots/hello-world'
fullPath: '/screenshots/hello-world'
preLoaderRoute: typeof ScreenshotsHelloWorldRouteImport
parentRoute: typeof rootRouteImport
}
'/info/getting-started': { '/info/getting-started': {
id: '/info/getting-started' id: '/info/getting-started'
path: '/info/getting-started' path: '/info/getting-started'
@ -3130,6 +3168,13 @@ declare module '@tanstack/react-start/server' {
preLoaderRoute: typeof AuthCallbackServerRouteImport preLoaderRoute: typeof AuthCallbackServerRouteImport
parentRoute: typeof rootServerRouteImport parentRoute: typeof rootServerRouteImport
} }
'/screenshots/metrics/$metricId': {
id: '/screenshots/metrics/$metricId'
path: '/screenshots/metrics/$metricId'
fullPath: '/screenshots/metrics/$metricId'
preLoaderRoute: typeof ScreenshotsMetricsMetricIdServerRouteImport
parentRoute: typeof rootServerRouteImport
}
} }
} }
@ -4140,6 +4185,7 @@ const rootRouteChildren: RootRouteChildren = {
EmbedRoute: EmbedRouteWithChildren, EmbedRoute: EmbedRouteWithChildren,
HealthcheckRoute: HealthcheckRoute, HealthcheckRoute: HealthcheckRoute,
InfoGettingStartedRoute: InfoGettingStartedRoute, InfoGettingStartedRoute: InfoGettingStartedRoute,
ScreenshotsHelloWorldRoute: ScreenshotsHelloWorldRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
@ -4147,6 +4193,7 @@ export const routeTree = rootRouteImport
const rootServerRouteChildren: RootServerRouteChildren = { const rootServerRouteChildren: RootServerRouteChildren = {
AuthCallbackServerRoute: AuthCallbackServerRoute, AuthCallbackServerRoute: AuthCallbackServerRoute,
AuthConfirmServerRoute: AuthConfirmServerRoute, AuthConfirmServerRoute: AuthConfirmServerRoute,
ScreenshotsMetricsMetricIdServerRoute: ScreenshotsMetricsMetricIdServerRoute,
} }
export const serverRouteTree = rootServerRouteImport export const serverRouteTree = rootServerRouteImport
._addFileChildren(rootServerRouteChildren) ._addFileChildren(rootServerRouteChildren)

View File

@ -0,0 +1,24 @@
import { createFileRoute } from '@tanstack/react-router';
import { useMount } from '@/hooks/useMount';
export const Route = createFileRoute('/screenshots/hello-world')({
component: RouteComponent,
});
function RouteComponent() {
useMount(() => {
// getMetricScreenshot({
// data: {
// metricId: '123',
// },
// }).then((res) => {
// console.log(res);
// });
});
return (
<div className="p-10 flex flex-col h-full border-red-500 border-10 items-center justify-center bg-blue-100 text-2xl text-blue-500">
Hello "/screenshot/hello-world"!
</div>
);
}

View File

@ -0,0 +1,59 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { chromium } from 'playwright';
import { z } from 'zod';
import { Route as HelloWorldRoute } from './hello-world';
const GetMetricScreenshotParamsSchema = z.object({
metricId: z.string(),
});
const GetMetricScreenshotQuerySchema = z.object({
version_number: z.coerce.number().min(1).optional(),
width: z.coerce.number().min(100).max(3840).default(800),
height: z.coerce.number().min(100).max(2160).default(450),
});
export const ServerRoute = createServerFileRoute('/screenshots/metrics/$metricId').methods({
GET: async ({ request, params }) => {
console.time('capture screenshot');
const { metricId } = GetMetricScreenshotParamsSchema.parse(params);
const { version_number, width, height } = GetMetricScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
const origin = new URL(request.url).origin;
console.timeLog('capture screenshot', 'params parsed');
const browser = await chromium.launch();
console.timeLog('capture screenshot', 'browser launched');
try {
const page = await browser.newPage({
viewport: { width, height },
});
console.timeLog('capture screenshot', 'page created');
const fullPath = `${origin}${HelloWorldRoute.fullPath}`;
await page.goto(fullPath, { waitUntil: 'networkidle' });
console.timeLog('capture screenshot', 'page navigated');
const screenshotBuffer = await page.screenshot({
type: 'png',
});
console.timeLog('capture screenshot', 'screenshot taken');
console.timeEnd('capture screenshot');
return new Response(new Uint8Array(screenshotBuffer), {
headers: {
'Content-Type': 'image/png',
'Content-Length': screenshotBuffer.length.toString(),
},
});
} catch (error) {
// console.error('Error capturing metric screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
} finally {
await browser.close();
}
},
});