feat(ai): add ai chat to dashboard

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-04-15 14:30:21 +02:00
parent 804a9c8056
commit 34769a5d58
46 changed files with 2624 additions and 1449 deletions

View File

@@ -0,0 +1,31 @@
import Chat from '@/components/chat/chat';
import { db, getOrganizationBySlug } from '@openpanel/db';
import type { UIMessage } from 'ai';
export default async function ChatPage({
params,
}: {
params: { organizationSlug: string; projectId: string };
}) {
const { projectId } = await params;
const [organization, chat] = await Promise.all([
getOrganizationBySlug(params.organizationSlug),
db.chat.findFirst({
where: {
projectId,
},
orderBy: {
createdAt: 'desc',
},
}),
]);
const messages = ((chat?.messages as UIMessage[]) || []).slice(-10);
return (
<Chat
projectId={projectId}
initialMessages={messages}
organization={organization}
/>
);
}

View File

@@ -1,5 +1,6 @@
'use client';
import { cn } from '@/utils/cn';
import { useSelectedLayoutSegments } from 'next/navigation';
const NOT_MIGRATED_PAGES = ['reports'];
@@ -16,6 +17,13 @@ export default function LayoutContent({
}
return (
<div className="pb-20 transition-all max-lg:mt-12 lg:pl-72">{children}</div>
<div
className={cn(
'pb-20 transition-all max-lg:mt-12 lg:pl-72',
segments.includes('chat') && 'pb-0',
)}
>
{children}
</div>
);
}

View File

@@ -15,6 +15,7 @@ import {
PlusIcon,
ScanEyeIcon,
ServerIcon,
SparklesIcon,
UsersIcon,
WallpaperIcon,
} from 'lucide-react';
@@ -23,6 +24,7 @@ import { usePathname } from 'next/navigation';
import { ProjectLink } from '@/components/links';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { CommandShortcut } from '@/components/ui/command';
import { useNumber } from '@/hooks/useNumerFormatter';
import type { IServiceDashboards, IServiceOrganization } from '@openpanel/db';
import { differenceInDays, format } from 'date-fns';
@@ -174,15 +176,25 @@ export default function LayoutMenu({
</div>
</ProjectLink>
)}
<ProjectLink
href={'/chat'}
className={cn('rounded p-2 row gap-2 hover:bg-def-200 items-center')}
>
<SparklesIcon size={20} />
<div className="flex-1 col gap-1">
<div className="font-medium">Ask AI</div>
</div>
<CommandShortcut>K</CommandShortcut>
</ProjectLink>
<ProjectLink
href={'/reports'}
className={cn('rounded p-2 row gap-2 hover:bg-def-200')}
className={cn('rounded p-2 row gap-2 hover:bg-def-200 items-center')}
>
<ChartLineIcon size={20} />
<div className="flex-1 col gap-1">
<div className="font-medium">Create report</div>
</div>
<PlusIcon size={16} className="text-muted-foreground" />
<CommandShortcut>J</CommandShortcut>
</ProjectLink>
</div>
<LinkWithIcon icon={WallpaperIcon} label="Overview" href={'/'} />

View File

@@ -7,8 +7,10 @@ import { ReportInterval } from '@/components/report/ReportInterval';
import { ReportLineType } from '@/components/report/ReportLineType';
import { ReportSaveButton } from '@/components/report/ReportSaveButton';
import {
changeChartType,
changeDateRanges,
changeEndDate,
changeInterval,
changeStartDate,
ready,
reset,
@@ -74,7 +76,13 @@ export default function ReportEditor({
</div>
</SheetTrigger>
<div className="col-span-4 grid grid-cols-2 gap-2 md:grid-cols-4">
<ReportChartType className="min-w-0 flex-1" />
<ReportChartType
className="min-w-0 flex-1"
onChange={(type) => {
dispatch(changeChartType(type));
}}
value={report.chartType}
/>
<TimeWindowPicker
className="min-w-0 flex-1"
onChange={(value) => {
@@ -90,7 +98,13 @@ export default function ReportEditor({
endDate={report.endDate}
startDate={report.startDate}
/>
<ReportInterval className="min-w-0 flex-1" />
<ReportInterval
className="min-w-0 flex-1"
interval={report.interval}
onChange={(newInterval) => dispatch(changeInterval(newInterval))}
range={report.range}
chartType={report.chartType}
/>
<ReportLineType className="min-w-0 flex-1" />
</div>
<div className="col-start-2 row-start-1 text-right md:col-start-6">

View File

@@ -5,6 +5,7 @@ import Providers from './providers';
import '@/styles/globals.css';
import 'flag-icons/css/flag-icons.min.css';
import 'katex/dist/katex.min.css';
import { GeistMono } from 'geist/font/mono';
import { GeistSans } from 'geist/font/sans';

View File

@@ -1,5 +1,7 @@
import type { MetadataRoute } from 'next';
export const dynamic = 'static';
export default function manifest(): MetadataRoute.Manifest {
return {
id: process.env.NEXT_PUBLIC_DASHBOARD_URL,

View File

@@ -54,34 +54,34 @@ function AllProviders({ children }: { children: React.ReactNode }) {
}
return (
<ThemeProvider
attribute="class"
disableTransitionOnChange
defaultTheme="system"
>
{process.env.NEXT_PUBLIC_OP_CLIENT_ID && (
<OpenPanelComponent
clientId={process.env.NEXT_PUBLIC_OP_CLIENT_ID}
trackScreenViews
trackOutgoingLinks
trackAttributes
/>
)}
<ReduxProvider store={storeRef.current}>
<api.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<NuqsAdapter>
<ThemeProvider
attribute="class"
disableTransitionOnChange
defaultTheme="system"
>
{process.env.NEXT_PUBLIC_OP_CLIENT_ID && (
<OpenPanelComponent
clientId={process.env.NEXT_PUBLIC_OP_CLIENT_ID}
trackScreenViews
trackOutgoingLinks
trackAttributes
/>
)}
<ReduxProvider store={storeRef.current}>
<api.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<TooltipProvider delayDuration={200}>
{children}
<NotificationProvider />
<Toaster />
<ModalProvider />
</TooltipProvider>
</NuqsAdapter>
</QueryClientProvider>
</api.Provider>
</ReduxProvider>
</ThemeProvider>
</QueryClientProvider>
</api.Provider>
</ReduxProvider>
</ThemeProvider>
</NuqsAdapter>
);
}