upgrade to next.js 15

This commit is contained in:
Ramnique Singh 2025-06-24 12:07:30 +05:30
parent 8096eaf63b
commit 3b72de5df4
61 changed files with 5344 additions and 11135 deletions

View file

@ -1,5 +1,5 @@
"use server";
import { getSession } from "@auth0/nextjs-auth0";
import { auth0 } from "../lib/auth0";
import { USE_AUTH } from "../lib/feature_flags";
import { WithStringId, User } from "../lib/types/types";
import { getUserFromSessionId, GUEST_DB_USER } from "../lib/auth";
@ -12,7 +12,7 @@ export async function authCheck(): Promise<WithStringId<z.infer<typeof User>>> {
return GUEST_DB_USER;
}
const { user } = await getSession() || {};
const { user } = await auth0.getSession() || {};
if (!user) {
throw new Error('User not authenticated');
}

View file

@ -777,7 +777,7 @@ export async function generateServerAuthUrl(
await projectAuthCheck(projectId);
// Get the origin from request headers
const headersList = headers();
const headersList = await headers();
const host = headersList.get('host') || '';
const protocol = headersList.get('x-forwarded-proto') || 'http';
const origin = `${protocol}://${host}`;

View file

@ -1,10 +0,0 @@
// pages/api/auth/[auth0].js
import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';
export const GET = handleAuth({
login: handleLogin({
authorizationParams: {
prompt: 'login'
}
})
});

View file

@ -3,7 +3,8 @@ import { USE_BILLING } from "@/app/lib/feature_flags";
import { redisClient } from "@/app/lib/redis";
import { CopilotAPIRequest } from "@/app/lib/types/copilot_types";
export async function GET(request: Request, { params }: { params: { streamId: string } }) {
export async function GET(request: Request, props: { params: Promise<{ streamId: string }> }) {
const params = await props.params;
// get the payload from redis
const payload = await redisClient.get(`copilot-stream-${params.streamId}`);
if (!payload) {

View file

@ -4,7 +4,8 @@ import { redisClient } from "@/app/lib/redis";
import { AgenticAPIChatMessage, AgenticAPIChatRequest, convertFromAgenticAPIChatMessages } from "@/app/lib/types/agents_api_types";
import { createParser, type EventSourceMessage } from 'eventsource-parser';
export async function GET(request: Request, { params }: { params: { streamId: string } }) {
export async function GET(request: Request, props: { params: Promise<{ streamId: string }> }) {
const params = await props.params;
// get the payload from redis
const payload = await redisClient.get(`chat-stream-${params.streamId}`);
if (!payload) {

View file

@ -8,10 +8,8 @@ import { ObjectId } from 'mongodb';
const UPLOADS_DIR = process.env.RAG_UPLOADS_DIR || '/uploads';
// PUT endpoint to handle file uploads
export async function PUT(
request: NextRequest,
{ params }: { params: { fileId: string } }
) {
export async function PUT(request: NextRequest, props: { params: Promise<{ fileId: string }> }) {
const params = await props.params;
const fileId = params.fileId;
if (!fileId) {
return NextResponse.json({ error: 'Missing file ID' }, { status: 400 });
@ -34,10 +32,8 @@ export async function PUT(
}
// GET endpoint to handle file downloads
export async function GET(
request: NextRequest,
{ params }: { params: { fileId: string } }
) {
export async function GET(request: NextRequest, props: { params: Promise<{ fileId: string }> }) {
const params = await props.params;
const fileId = params.fileId;
if (!fileId) {
return NextResponse.json({ error: 'Missing file ID' }, { status: 400 });

View file

@ -3,10 +3,8 @@ import { chatsCollection } from "../../../../../../lib/mongodb";
import { ObjectId } from "mongodb";
import { authCheck } from "../../../utils";
export async function POST(
request: NextRequest,
{ params }: { params: { chatId: string } }
): Promise<Response> {
export async function POST(request: NextRequest, props: { params: Promise<{ chatId: string }> }): Promise<Response> {
const params = await props.params;
return await authCheck(request, async (session) => {
const { chatId } = params;

View file

@ -6,10 +6,8 @@ import { Filter, ObjectId } from "mongodb";
import { authCheck } from "../../../utils";
// list messages
export async function GET(
req: NextRequest,
{ params }: { params: { chatId: string } }
): Promise<Response> {
export async function GET(req: NextRequest, props: { params: Promise<{ chatId: string }> }): Promise<Response> {
const params = await props.params;
return await authCheck(req, async (session) => {
const { chatId } = params;

View file

@ -1,23 +1,21 @@
'use client';
import { TypewriterEffect } from "./lib/components/typewriter";
import Image from 'next/image';
import logo from "@/public/logo.png";
import { useUser } from "@auth0/nextjs-auth0/client";
import { useUser } from "@auth0/nextjs-auth0";
import { useRouter } from "next/navigation";
import { Spinner } from "@heroui/react";
import { LogInIcon } from "lucide-react";
export function App() {
const router = useRouter();
const { user, error, isLoading } = useUser();
const { user, isLoading } = useUser();
if (user) {
router.push("/projects");
}
// Add auto-redirect for non-authenticated users
if (!isLoading && !user && !error) {
router.push("/api/auth/login");
if (!isLoading && !user) {
router.push("/auth/login");
}
return (
@ -30,8 +28,7 @@ export function App() {
alt="RowBoat Logo"
height={40}
/>
{(isLoading || (!user && !error)) && <Spinner size="sm" />}
{error && <div className="text-red-500">{error.message}</div>}
{(isLoading || !user) && <Spinner size="sm" />}
{user && <div className="flex items-center gap-2">
<Spinner size="sm" />
<div className="text-sm text-gray-400">Welcome, {user.name}</div>

View file

@ -4,13 +4,14 @@ import { redirect } from "next/navigation";
export const dynamic = 'force-dynamic';
export default async function Page({
searchParams,
}: {
searchParams: {
redirect: string;
export default async function Page(
props: {
searchParams: Promise<{
redirect: string;
}>
}
}) {
) {
const searchParams = await props.searchParams;
const customer = await requireBillingCustomer();
await syncWithStripe(customer._id);
const redirectUrl = searchParams.redirect as string;

View file

@ -1,7 +1,13 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
@import 'tailwindcss';
@import './styles/quill-mentions.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
@plugin './hero.ts';
@source '../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
@custom-variant dark (&:is(.dark *));
@reference 'tailwindcss';
@layer utilities {
.text-balance {
@ -106,74 +112,16 @@ html, body {
input, textarea, select {
@apply rounded-lg border-[#E5E7EB] dark:border-[#2E2E30]
bg-[#F3F4F6] dark:bg-[#2A2A2D]
focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50
focus:ring-2 focus:ring-indigo-500/50
transition-all duration-200;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
.card-shadow {
@apply shadow-sm dark:shadow-none dark:border-border;
}
.hover-effect {
@apply hover:bg-accent/10 dark:hover:bg-accent/20 transition-colors;
}
.border-subtle {
@apply border-border dark:border-border/50;
}
/* Apply rounded corners to common interactive elements by default */
button,
input,
textarea,
select,
[role="button"],
.card,
.input,
.select,
.textarea,
.button {
@apply !rounded-lg;
}
}
* {
-webkit-transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out, opacity 0.2s ease-in-out !important;
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out, opacity 0.2s ease-in-out !important;
}
* {
@apply transition-colors duration-200;
}
/* Add Inter font */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
/* Set base font */
html {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
@keyframes slideUpAndFade {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
body {
background: var(--background);
color: var(--foreground);
}
.animate-slideUpAndFade {
animation: slideUpAndFade 0.2s ease-out forwards;
}

3
apps/rowboat/app/hero.ts Normal file
View file

@ -0,0 +1,3 @@
// hero.ts
import { heroui } from "@heroui/react";
export default heroui();

View file

@ -1,10 +1,10 @@
import "./globals.css";
import { ThemeProvider } from "./providers/theme-provider";
import { UserProvider } from '@auth0/nextjs-auth0/client';
import { Inter } from "next/font/google";
import { Providers } from "./providers";
import { Metadata } from "next";
import { HelpModalProvider } from "./providers/help-modal-provider";
import { Auth0Provider } from "@auth0/nextjs-auth0";
const inter = Inter({ subsets: ["latin"] });
@ -21,7 +21,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return <html lang="en" className="h-dvh">
<UserProvider>
<Auth0Provider>
<ThemeProvider>
<body className={`${inter.className} h-full text-base [scrollbar-width:thin] bg-background`}>
<Providers className='h-full flex flex-col'>
@ -31,6 +31,6 @@ export default function RootLayout({
</Providers>
</body>
</ThemeProvider>
</UserProvider>
</Auth0Provider>
</html>;
}

View file

@ -1,13 +1,12 @@
import { z } from "zod";
import { Claims } from "@auth0/nextjs-auth0";
import { ObjectId } from "mongodb";
import { usersCollection, projectsCollection, projectMembersCollection } from "./mongodb";
import { getSession } from "@auth0/nextjs-auth0";
import { auth0 } from "./auth0";
import { User, WithStringId } from "./types/types";
import { USE_AUTH } from "./feature_flags";
import { redirect } from "next/navigation";
export const GUEST_SESSION: Claims = {
export const GUEST_SESSION = {
email: "guest@rowboatlabs.com",
email_verified: true,
sub: "guest_user",
@ -39,9 +38,9 @@ export async function requireAuth(): Promise<WithStringId<z.infer<typeof User>>>
return GUEST_DB_USER;
}
const { user } = await getSession() || {};
const { user } = await auth0.getSession() || {};
if (!user) {
redirect('/api/auth/login');
redirect('/auth/login');
}
// fetch db user

View file

@ -0,0 +1,21 @@
// lib/auth0.js
import { Auth0Client } from "@auth0/nextjs-auth0/server";
// Initialize the Auth0 client
export const auth0 = new Auth0Client({
// Options are loaded from environment variables by default
// Ensure necessary environment variables are properly set
domain: process.env.AUTH0_ISSUER_BASE_URL,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
appBaseUrl: process.env.AUTH0_BASE_URL,
secret: process.env.AUTH0_SECRET,
authorizationParameters: {
// In v4, the AUTH0_SCOPE and AUTH0_AUDIENCE environment variables for API authorized applications are no longer automatically picked up by the SDK.
// Instead, we need to provide the values explicitly.
scope: process.env.AUTH0_SCOPE,
audience: process.env.AUTH0_AUDIENCE,
}
});

View file

@ -3,7 +3,6 @@ import { z } from 'zod';
import { Customer, AuthorizeRequest, AuthorizeResponse, LogUsageRequest, UsageResponse, CustomerPortalSessionResponse, PricesResponse, UpdateSubscriptionPlanRequest, UpdateSubscriptionPlanResponse, ModelsResponse } from './types/billing_types';
import { ObjectId } from 'mongodb';
import { projectsCollection, usersCollection } from './mongodb';
import { getSession } from '@auth0/nextjs-auth0';
import { redirect } from 'next/navigation';
import { getUserFromSessionId, requireAuth } from './auth';
import { USE_BILLING } from './feature_flags';

View file

@ -9,103 +9,104 @@ export default function MarkdownContent({
content: string;
atValues?: Match[];
}) {
return <Markdown
className="overflow-auto break-words"
remarkPlugins={[remarkGfm]}
components={{
h1({ children }) {
return <h1 className="text-xl font-bold py-2">{children}</h1>
},
h2({ children }) {
return <h2 className="text-lg font-bold py-2">{children}</h2>
},
h3({ children }) {
return <h3 className="text-base font-semibold py-2">{children}</h3>
},
h4({ children }) {
return <h4 className="text-sm font-semibold py-2">{children}</h4>
},
h5({ children }) {
return <h5 className="text-xs font-semibold py-2">{children}</h5>
},
h6({ children }) {
return <h6 className="text-xs font-semibold py-2">{children}</h6>
},
strong({ children }) {
return <span className="font-semibold">{children}</span>
},
p({ children }) {
return <p className="py-2">{children}</p>
},
ul({ children }) {
return <ul className="py-2 pl-5 list-disc">{children}</ul>
},
ol({ children }) {
return <ul className="py-2 pl-5 list-decimal">{children}</ul>
},
table({ children }) {
return <table className="py-2 border-collapse border border-gray-400 rounded">{children}</table>
},
th({ children }) {
return <th className="px-2 py-1 border-collapse border border-gray-300 rounded">{children}</th>
},
td({ children }) {
return <td className="px-2 py-1 border-collapse border border-gray-300 rounded">{children}</td>
},
blockquote({ children }) {
return <blockquote className='py-2 bg-gray-200 px-1'>{children}</blockquote>;
},
a(props) {
const { children, href, className, node, ...rest } = props;
return <div className="overflow-auto break-words">
<Markdown
remarkPlugins={[remarkGfm]}
components={{
h1({ children }) {
return <h1 className="text-xl font-bold py-2">{children}</h1>
},
h2({ children }) {
return <h2 className="text-lg font-bold py-2">{children}</h2>
},
h3({ children }) {
return <h3 className="text-base font-semibold py-2">{children}</h3>
},
h4({ children }) {
return <h4 className="text-sm font-semibold py-2">{children}</h4>
},
h5({ children }) {
return <h5 className="text-xs font-semibold py-2">{children}</h5>
},
h6({ children }) {
return <h6 className="text-xs font-semibold py-2">{children}</h6>
},
strong({ children }) {
return <span className="font-semibold">{children}</span>
},
p({ children }) {
return <p className="py-2">{children}</p>
},
ul({ children }) {
return <ul className="py-2 pl-5 list-disc">{children}</ul>
},
ol({ children }) {
return <ul className="py-2 pl-5 list-decimal">{children}</ul>
},
table({ children }) {
return <table className="py-2 border-collapse border border-gray-400 rounded">{children}</table>
},
th({ children }) {
return <th className="px-2 py-1 border-collapse border border-gray-300 rounded">{children}</th>
},
td({ children }) {
return <td className="px-2 py-1 border-collapse border border-gray-300 rounded">{children}</td>
},
blockquote({ children }) {
return <blockquote className='py-2 bg-gray-200 px-1'>{children}</blockquote>;
},
a(props) {
const { children, href, className, node, ...rest } = props;
// If this is a mention link, render it with mention styling
if (href === '#mention') {
let label: string = '';
// Check if children is an array and get the first text element
if (Array.isArray(children) && children.length > 0) {
const text = children[0];
if (typeof text === 'string') {
const parts = text.split('@');
// If this is a mention link, render it with mention styling
if (href === '#mention') {
let label: string = '';
// Check if children is an array and get the first text element
if (Array.isArray(children) && children.length > 0) {
const text = children[0];
if (typeof text === 'string') {
const parts = text.split('@');
if (parts.length === 2) {
label = parts[1];
}
}
} else if (typeof children === 'string') {
// Fallback for direct string children
const parts = children.split('@');
if (parts.length === 2) {
label = parts[1];
}
}
} else if (typeof children === 'string') {
// Fallback for direct string children
const parts = children.split('@');
if (parts.length === 2) {
label = parts[1];
}
}
// check if the the mention is valid
const invalid = !atValues.some(atValue => atValue.id === label);
if (atValues.length > 0 && invalid) {
// check if the the mention is valid
const invalid = !atValues.some(atValue => atValue.id === label);
if (atValues.length > 0 && invalid) {
return (
<span className="inline-block bg-[#e0f2fe] text-[red] px-1.5 py-0.5 rounded whitespace-nowrap">
@{label} (!)
</span>
);
}
return (
<span className="inline-block bg-[#e0f2fe] text-[red] px-1.5 py-0.5 rounded whitespace-nowrap">
@{label} (!)
<span className="inline-block bg-[#e0f2fe] text-[#1e40af] px-1.5 py-0.5 rounded whitespace-nowrap">
@{label}
</span>
);
}
return (
<span className="inline-block bg-[#e0f2fe] text-[#1e40af] px-1.5 py-0.5 rounded whitespace-nowrap">
@{label}
</span>
);
}
// Otherwise render normal link (your existing link component)
return <a className="inline-flex items-center gap-1" target="_blank" href={href} {...rest} >
<span className='underline'>
{children}
</span>
<svg className="w-[16px] h-[16px]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778" />
</svg>
</a>
},
}}
>
{content}
</Markdown>;
// Otherwise render normal link (your existing link component)
return <a className="inline-flex items-center gap-1" target="_blank" href={href} {...rest} >
<span className='underline'>
{children}
</span>
<svg className="w-[16px] h-[16px]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778" />
</svg>
</a>
},
}}
>
{content}
</Markdown>
</div>;
}

View file

@ -1,3 +1,5 @@
@import "../../globals.css";
/* Target both edit mode and view mode mentions */
.mention,
.ql-editor p span[class*="bg-[#e"], /* Matches both #e8f2fe and #e0f2fe */
@ -15,12 +17,12 @@ span[class*="bg-[#e"] { /* For view mode */
}
/* Handle Next.js dark mode class if needed */
:global(.dark) .mention,
/* :global(.dark) .mention,
:global(.dark) .ql-editor p span[class*="bg-[#e"],
:global(.dark) span[class*="bg-[#e"] {
background-color: rgb(31 41 55) !important;
color: rgb(243 244 246) !important;
}
} */
/* Override the inline styles */
.ql-editor p span[class*="bg-[#e0f2fe]"],

View file

@ -26,7 +26,7 @@ export function ListItem({
onClick: () => void;
disabled?: boolean;
rightElement?: React.ReactNode;
selectedRef?: React.RefObject<HTMLButtonElement>;
selectedRef?: React.RefObject<HTMLButtonElement | null>;
icon?: React.ReactNode;
}) {
return (

View file

@ -1,5 +1,5 @@
'use client';
import { useUser } from '@auth0/nextjs-auth0/client';
import { useUser } from '@auth0/nextjs-auth0';
import { Avatar, Dropdown, DropdownItem, DropdownSection, DropdownTrigger, DropdownMenu } from "@heroui/react";
import { useRouter } from 'next/navigation';
import Link from 'next/link';

View file

@ -1,3 +1,4 @@
'use client';
import { Spinner } from "@heroui/react";
export default function Loading() {

View file

@ -27,8 +27,8 @@ export function Section({
title: string;
children: React.ReactNode;
}) {
return <div className="w-full flex flex-col gap-4 border border-border p-4 rounded-md">
<h2 className="font-semibold pb-2 border-b border-border">{title}</h2>
return <div className="w-full flex flex-col gap-4 border border p-4 rounded-md">
<h2 className="font-semibold pb-2 border-b border">{title}</h2>
{children}
</div>;
}
@ -198,9 +198,9 @@ export function ApiKeysSection({
<Divider />
{loading && <Spinner size="sm" />}
{!loading && <div className="border border-border rounded-lg text-sm">
<div className="flex items-center border-b border-border p-4">
<div className="flex-[3] font-normal">API Key</div>
{!loading && <div className="border border rounded-lg text-sm">
<div className="flex items-center border-b border p-4">
<div className="flex-3 font-normal">API Key</div>
<div className="flex-1 font-normal">Created</div>
<div className="flex-1 font-normal">Last Used</div>
<div className="w-10"></div>
@ -216,8 +216,8 @@ export function ApiKeysSection({
</div>}
<div className="flex flex-col">
{keys.map((key) => (
<div key={key._id} className="flex items-start border-b border-border last:border-b-0 p-4">
<div className="flex-[3] p-2">
<div key={key._id} className="flex items-start border-b border last:border-b-0 p-4">
<div className="flex-3 p-2">
<ApiKeyDisplay apiKey={key.key} />
</div>
<div className="flex-1 p-2">

View file

@ -7,13 +7,14 @@ export const metadata: Metadata = {
title: "Project config",
};
export default async function Page({
params,
}: {
params: {
projectId: string;
};
}) {
export default async function Page(
props: {
params: Promise<{
projectId: string;
}>;
}
) {
const params = await props.params;
await requireActiveBillingSubscription();
return <App
projectId={params.projectId}

View file

@ -2,7 +2,7 @@ export default async function Layout({
params,
children
}: {
params: { projectId: string }
params: Promise<{ projectId: string }>
children: React.ReactNode
}) {
return children;

View file

@ -1,11 +1,12 @@
import { redirect } from "next/navigation";
import { requireActiveBillingSubscription } from '@/app/lib/billing';
export default async function Page({
params
}: {
params: { projectId: string }
}) {
export default async function Page(
props: {
params: Promise<{ projectId: string }>
}
) {
const params = await props.params;
await requireActiveBillingSubscription();
redirect(`/projects/${params.projectId}/workflow`);
}

View file

@ -1,14 +1,15 @@
import { SourcePage } from "./source-page";
import { requireActiveBillingSubscription } from '@/app/lib/billing';
export default async function Page({
params,
}: {
params: {
projectId: string,
sourceId: string
export default async function Page(
props: {
params: Promise<{
projectId: string,
sourceId: string
}>
}
}) {
) {
const params = await props.params;
await requireActiveBillingSubscription();
return <SourcePage projectId={params.projectId} sourceId={params.sourceId} />;
}

View file

@ -37,7 +37,7 @@ export function SectionRow({ children, className }: { children: ReactNode; class
export function SectionLabel({ children, className }: { children: ReactNode; className?: string }) {
return (
<div className={`w-24 flex-shrink-0 text-sm text-gray-500 dark:text-gray-400 ${className || ''}`}>
<div className={`w-24 shrink-0 text-sm text-gray-500 dark:text-gray-400 ${className || ''}`}>
{children}
</div>
);

View file

@ -28,7 +28,7 @@ export function SourceStatus({
{status === 'pending' && (
<>
<div className="flex-shrink-0">
<div className="shrink-0">
<Spinner size="sm" className="text-blue-500 dark:text-blue-400" />
</div>
<div className="flex flex-col">

View file

@ -36,7 +36,7 @@ export function ToggleSource({
onClick={handleToggle}
disabled={loading}
className={`
relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500/20
${isActive ? 'bg-indigo-500' : 'bg-gray-200 dark:bg-gray-700'}
disabled:opacity-50 disabled:cursor-not-allowed

View file

@ -8,11 +8,12 @@ export const metadata: Metadata = {
title: "Add data source"
}
export default async function Page({
params
}: {
params: { projectId: string }
}) {
export default async function Page(
props: {
params: Promise<{ projectId: string }>
}
) {
const params = await props.params;
await requireActiveBillingSubscription();
if (!USE_RAG) {
redirect(`/projects/${params.projectId}`);

View file

@ -6,11 +6,12 @@ export const metadata: Metadata = {
title: "Data sources",
}
export default async function Page({
params,
}: {
params: { projectId: string }
}) {
export default async function Page(
props: {
params: Promise<{ projectId: string }>
}
) {
const params = await props.params;
await requireActiveBillingSubscription();
return <SourcesList
projectId={params.projectId}

View file

@ -9,7 +9,7 @@ interface ProfileFormProps {
mockTools?: boolean;
mockPrompt?: string;
};
formRef: React.RefObject<HTMLFormElement>;
formRef: React.RefObject<HTMLFormElement | null>;
handleSubmit: (formData: FormData) => Promise<void>;
onCancel: () => void;
submitButtonText: string;

View file

@ -2,7 +2,7 @@ import { FormStatusButton } from "@/app/lib/components/form-status-button";
import { Button, Input, Textarea } from "@heroui/react";
interface ScenarioFormProps {
formRef: React.RefObject<HTMLFormElement>;
formRef: React.RefObject<HTMLFormElement | null>;
handleSubmit: (formData: FormData) => Promise<void>;
onCancel: () => void;
submitButtonText: string;

View file

@ -7,7 +7,7 @@ import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/com
import { z } from "zod";
interface SimulationFormProps {
formRef: React.RefObject<HTMLFormElement>;
formRef: React.RefObject<HTMLFormElement | null>;
handleSubmit: (formData: FormData) => Promise<void>;
scenario: WithStringId<z.infer<typeof TestScenario>> | null;
setScenario: (scenario: WithStringId<z.infer<typeof TestScenario>> | null) => void;

View file

@ -6,11 +6,12 @@ import { RunsApp } from "./runs_app";
import { TestingMenu } from "./testing_menu";
import { requireActiveBillingSubscription } from '@/app/lib/billing';
export default async function TestPage({ params }: { params: { projectId: string; slug?: string[] } }) {
export default async function TestPage(props: { params: Promise<{ projectId: string; slug?: string[] }> }) {
const params = await props.params;
await requireActiveBillingSubscription();
const { projectId, slug = [] } = params;
let app: "scenarios" | "simulations" | "profiles" | "runs" = "runs";
if (slug[0] === "scenarios") {
app = "scenarios";
} else if (slug[0] === "simulations") {

View file

@ -295,7 +295,7 @@ export function CustomServers() {
<div className="space-y-6">
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-800 rounded-lg p-4">
<div className="flex gap-3">
<div className="flex-shrink-0">
<div className="shrink-0">
<Info className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<p className="text-sm text-blue-700 dark:text-blue-300">

View file

@ -578,7 +578,7 @@ export function HostedServers({ onSwitchTab }: HostedServersProps) {
<div className="space-y-6">
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-800 rounded-lg p-4">
<div className="flex gap-3">
<div className="flex-shrink-0">
<div className="shrink-0">
<Info className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<p className="text-sm text-blue-700 dark:text-blue-300">

View file

@ -193,7 +193,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
value={item || ''}
onChange={(e) => handleArrayItemChange(index, e.target.value)}
placeholder="Enter value"
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
) : itemSchema.type === 'number' || itemSchema.type === 'integer' ? (
<Input
@ -209,7 +209,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
handleArrayItemChange(index, isNaN(val) ? '' : val);
}}
placeholder="Enter value"
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
) : itemSchema.type === 'boolean' ? (
<div className="scale-75 origin-left">
@ -284,7 +284,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
className="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-700 rounded-md
bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100
focus:outline-none hover:border-gray-300 dark:hover:border-gray-600
focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0"
focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0!"
>
<option value="" disabled>Select {paramName}</option>
{schema.enum.map((opt: string) => (
@ -299,7 +299,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
type="datetime-local"
value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)}
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
);
}
@ -309,7 +309,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
type="date"
value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)}
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
);
}
@ -319,7 +319,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
type="time"
value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)}
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
);
}
@ -329,7 +329,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)}
placeholder={`Enter ${paramName}`}
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
);
@ -349,7 +349,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
handleParameterChange(paramName, isNaN(val) ? '' : val);
}}
placeholder={`Enter ${paramName}`}
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
);
@ -377,7 +377,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)}
placeholder={`Enter ${paramName}`}
className="focus:ring-0 focus:ring-offset-0 !ring-0 !ring-offset-0 focus:outline-none"
className="focus:ring-0 focus:ring-offset-0 ring-0! ring-offset-0! focus:outline-none"
/>
);
}

View file

@ -26,7 +26,7 @@ export function ToolsConfig() {
<Tab key="hosted" title={
<div className="flex items-center gap-2">
<span>Tools Library</span>
<span className="leading-none px-1.5 py-[2px] text-[9px] font-medium bg-gradient-to-r from-pink-500 to-violet-500 text-white rounded-full">
<span className="leading-none px-1.5 py-[2px] text-[9px] font-medium bg-linear-to-r from-pink-500 to-violet-500 text-white rounded-full">
BETA
</span>
</div>

View file

@ -32,7 +32,7 @@ function Section({ title, children, description }: {
export function WebhookConfig() {
const params = useParams();
const projectId = typeof params.projectId === 'string' ? params.projectId : params.projectId[0];
const projectId = params.projectId ? (typeof params.projectId === 'string' ? params.projectId : params.projectId[0]) : '';
const [loading, setLoading] = useState(true);
const [webhookUrl, setWebhookUrl] = useState<string | null>(null);

View file

@ -80,7 +80,7 @@ const ListItemWithMenu = ({
isSelected?: boolean;
onClick?: () => void;
disabled?: boolean;
selectedRef?: React.RefObject<HTMLButtonElement>;
selectedRef?: React.RefObject<HTMLButtonElement | null>;
menuContent: React.ReactNode;
statusLabel?: React.ReactNode;
icon?: React.ReactNode;
@ -111,7 +111,7 @@ const ListItemWithMenu = ({
}}
disabled={disabled}
>
<div className={clsx("flex-shrink-0 flex items-center justify-center w-4 h-4", iconClassName)}>
<div className={clsx("shrink-0 flex items-center justify-center w-4 h-4", iconClassName)}>
{mcpServerName ? (
<ServerLogo
serverName={mcpServerName}
@ -147,7 +147,7 @@ interface ServerCardProps {
} | null;
onSelectTool: (name: string) => void;
onDeleteTool: (name: string) => void;
selectedRef: React.RefObject<HTMLButtonElement>;
selectedRef: React.RefObject<HTMLButtonElement | null>;
}
const ServerCard = ({
@ -343,7 +343,7 @@ export function EntityList({
tourTarget="entity-agents"
className={clsx(
"h-full overflow-hidden",
!expandedPanels.agents && "!h-[53px]"
!expandedPanels.agents && "h-[53px]!"
)}
title={
<button
@ -429,7 +429,7 @@ export function EntityList({
tourTarget="entity-tools"
className={clsx(
"h-full overflow-hidden",
!expandedPanels.tools && "!h-[53px]"
!expandedPanels.tools && "h-[53px]!"
)}
title={
<button
@ -549,7 +549,7 @@ export function EntityList({
tourTarget="entity-prompts"
className={clsx(
"h-full overflow-hidden",
!expandedPanels.prompts && "!h-[53px]"
!expandedPanels.prompts && "h-[53px]!"
)}
title={
<button
@ -696,7 +696,7 @@ const SortableAgentItem = ({ agent, isSelected, onClick, selectedRef, statusLabe
agent: z.infer<typeof WorkflowAgent>;
isSelected?: boolean;
onClick?: () => void;
selectedRef?: React.RefObject<HTMLButtonElement>;
selectedRef?: React.RefObject<HTMLButtonElement | null>;
statusLabel?: React.ReactNode;
onToggle: (name: string) => void;
onSetMainAgent: (name: string) => void;

View file

@ -11,11 +11,12 @@ export const metadata: Metadata = {
title: "Workflow"
}
export default async function Page({
params,
}: {
params: { projectId: string };
}) {
export default async function Page(
props: {
params: Promise<{ projectId: string }>;
}
) {
const params = await props.params;
await requireActiveBillingSubscription();
console.log('->>> workflow page being rendered');
const project = await projectsCollection.findOne({

View file

@ -148,7 +148,7 @@ function PreviewModal({
</div>}
{view === 'markdown' && <div className="flex gap-1">
{oldValue !== undefined && <div className="w-1/2 flex flex-col border-r-2 border-gray-200 overflow-auto">
<div className="text-gray-800 font-semibold italic text-sm px-2 py-1 border-b-1 border-gray-200">Old</div>
<div className="text-gray-800 font-semibold italic text-sm px-2 py-1 border-b border-gray-200">Old</div>
<div className="p-2 overflow-auto">
<MarkdownContent
content={oldValue}
@ -158,7 +158,7 @@ function PreviewModal({
<div className={clsx("flex flex-col", {
'w-1/2': oldValue !== undefined
})}>
{oldValue !== undefined && <div className="text-gray-800 font-semibold italic text-sm px-2 py-1 border-b-1 border-gray-200">New</div>}
{oldValue !== undefined && <div className="text-gray-800 font-semibold italic text-sm px-2 py-1 border-b border-gray-200">New</div>}
<div className="p-2 overflow-auto">
<MarkdownContent
content={newValue}

View file

@ -580,7 +580,7 @@ export function WorkflowEditor({
eligibleModels: z.infer<typeof ModelsResponse> | "*";
}) {
const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, {
const [state, dispatch] = useReducer(reducer, {
patches: [],
inversePatches: [],
currentIndex: 0,

View file

@ -1,26 +1,20 @@
import Link from "next/link";
import { LucideIcon } from "lucide-react";
interface MenuItemProps {
href?: string;
icon: LucideIcon;
selected?: boolean;
collapsed?: boolean;
onClick?: () => void;
children?: React.ReactNode;
}
export default function MenuItem({
href,
icon: Icon,
selected = false,
collapsed = false,
onClick,
children
}: MenuItemProps) {
const ButtonContent = (
<button
onClick={onClick}
return (
<div
className={`
w-full px-3 py-2 rounded-md flex items-center gap-3
text-sm font-medium transition-all duration-200
@ -32,12 +26,6 @@ export default function MenuItem({
>
<Icon size={16} />
{!collapsed && children}
</button>
</div>
);
if (href) {
return <Link href={href}>{ButtonContent}</Link>;
}
return ButtonContent;
}

View file

@ -98,7 +98,7 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
return (
<>
<aside className={`${collapsed ? 'w-16' : 'w-60'} bg-transparent flex flex-col h-full transition-all duration-300`}>
<div className="flex flex-col flex-grow">
<div className="flex flex-col grow">
{!isProjectsRoute && (
<>
{/* Project Selector */}
@ -141,46 +141,42 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
>
<Link
href={isDisabled ? '#' : fullPath}
className={isDisabled ? 'pointer-events-none' : ''}
className={`
relative w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-2.5 py-3 gap-2.5'}
${isActive
? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-l-2 border-indigo-600 dark:border-indigo-400'
: isDisabled
? 'text-zinc-300 dark:text-zinc-600 cursor-not-allowed'
: 'text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800/50 hover:text-zinc-900 dark:hover:text-zinc-300'
}
${isDisabled ? 'pointer-events-none' : ''}
`}
data-tour-target={item.href === 'config' ? 'settings' : item.href === 'sources' ? 'entity-data-sources' : undefined}
>
<button
<Icon
size={collapsed ? COLLAPSED_ICON_SIZE : EXPANDED_ICON_SIZE}
className={`
relative w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-2.5 py-3 gap-2.5'}
${isActive
? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-l-2 border-indigo-600 dark:border-indigo-400'
: isDisabled
? 'text-zinc-300 dark:text-zinc-600 cursor-not-allowed'
: 'text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800/50 hover:text-zinc-900 dark:hover:text-zinc-300'
transition-all duration-200
${isDisabled
? 'text-zinc-300 dark:text-zinc-600'
: isActive
? 'text-indigo-600 dark:text-indigo-400'
: 'text-zinc-500 dark:text-zinc-400'
}
`}
disabled={isDisabled}
data-tour-target={item.href === 'config' ? 'settings' : item.href === 'sources' ? 'entity-data-sources' : undefined}
>
<Icon
size={collapsed ? COLLAPSED_ICON_SIZE : EXPANDED_ICON_SIZE}
className={`
transition-all duration-200
${isDisabled
? 'text-zinc-300 dark:text-zinc-600'
: isActive
? 'text-indigo-600 dark:text-indigo-400'
: 'text-zinc-500 dark:text-zinc-400'
}
`}
/>
{!collapsed && (
<>
<span>{item.label}</span>
{item.beta && (
<span className="ml-1.5 leading-none px-1.5 py-[2px] text-[9px] font-medium bg-gradient-to-r from-pink-500 to-violet-500 text-white rounded-full">
BETA
</span>
)}
</>
)}
</button>
/>
{!collapsed && (
<>
<span>{item.label}</span>
{item.beta && (
<span className="ml-1.5 leading-none px-1.5 py-[2px] text-[9px] font-medium bg-linear-to-r from-pink-500 to-violet-500 text-white rounded-full">
BETA
</span>
)}
</>
)}
</Link>
</Tooltip>
);

View file

@ -29,7 +29,7 @@ export function Nav({
setCollapsed(!collapsed);
}
return <div className={clsx("shrink-0 flex flex-col gap-2 border-r border-border relative p-2", {
return <div className={clsx("shrink-0 flex flex-col gap-2 border-r border relative p-2", {
"w-40": !collapsed,
"w-10": collapsed
})}>

View file

@ -46,7 +46,7 @@ export function SearchInput({
tokens.colors.dark.text.primary,
"placeholder:text-gray-400 dark:placeholder:text-gray-500",
"border border-gray-200 dark:border-gray-700",
"focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50",
"focus:ring-2 focus:ring-indigo-500/50",
"focus:border-transparent"
)}
/>
@ -66,7 +66,7 @@ export function SearchInput({
? "bg-indigo-600 text-white"
: "bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-400",
"hover:bg-gray-100 dark:hover:bg-gray-700",
"focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50"
"focus:outline-none focus:ring-2 focus:ring-indigo-500/50"
)}
>
{filter.charAt(0).toUpperCase() + filter.slice(1)}

View file

@ -99,8 +99,8 @@ export function ComposeBoxCopilot({
autoResize={true}
maxHeight={120}
className={`
!min-h-0
!border-0 !shadow-none !ring-0
min-h-0!
border-0! shadow-none! ring-0!
bg-transparent
resize-none
overflow-y-auto

View file

@ -78,8 +78,8 @@ export function ComposeBoxPlayground({
autoResize={true}
maxHeight={120}
className={`
!min-h-0
!border-0 !shadow-none !ring-0
min-h-0!
border-0! shadow-none! ring-0!
bg-transparent
resize-none
overflow-y-auto

View file

@ -93,8 +93,8 @@ export function ComposeBox({
autoResize={true}
maxHeight={120}
className={`
!min-h-0
!border-0 !shadow-none !ring-0
min-h-0!
border-0! shadow-none! ring-0!
bg-transparent
resize-none
overflow-y-auto

View file

@ -11,7 +11,7 @@ export function HelpModal({ isOpen, onClose, onStartTour }: HelpModalProps) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[100] flex items-center justify-center">
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-100 flex items-center justify-center">
<div className="bg-white dark:bg-zinc-800 rounded-lg shadow-lg p-6 w-[480px] max-w-[90vw] animate-in fade-in duration-200">
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">
Need Help?

View file

@ -61,7 +61,7 @@ export function Panel({
"flex flex-col overflow-hidden rounded-xl border relative",
variant === 'copilot' ? "border-blue-200 dark:border-blue-800" : "border-zinc-200 dark:border-zinc-800",
"bg-white dark:bg-zinc-900",
maxHeight ? "max-h-[var(--panel-height)]" : "h-full",
maxHeight ? "max-h-(--panel-height)" : "h-full",
className
)}
style={{

View file

@ -89,7 +89,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
return (
<>
{/* Top */}
<div className="fixed z-[100] backdrop-blur-sm bg-black/30" style={{
<div className="fixed z-100 backdrop-blur-sm bg-black/30" style={{
top: 0,
left: 0,
right: 0,
@ -97,7 +97,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
}} />
{/* Left */}
<div className="fixed z-[100] backdrop-blur-sm bg-black/30" style={{
<div className="fixed z-100 backdrop-blur-sm bg-black/30" style={{
top: Math.max(0, rect.top - padding),
left: 0,
width: Math.max(0, rect.left - padding),
@ -105,7 +105,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
}} />
{/* Right */}
<div className="fixed z-[100] backdrop-blur-sm bg-black/30" style={{
<div className="fixed z-100 backdrop-blur-sm bg-black/30" style={{
top: Math.max(0, rect.top - padding),
left: rect.right + padding,
right: 0,
@ -113,7 +113,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
}} />
{/* Bottom */}
<div className="fixed z-[100] backdrop-blur-sm bg-black/30" style={{
<div className="fixed z-100 backdrop-blur-sm bg-black/30" style={{
top: rect.bottom + padding,
left: 0,
right: 0,
@ -122,7 +122,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
{/* Highlight border around target */}
<div
className="fixed z-[100] border-2 border-white/50 rounded-lg pointer-events-none"
className="fixed z-100 border-2 border-white/50 rounded-lg pointer-events-none"
style={{
top: rect.top - padding,
left: rect.left - padding,

View file

@ -1,7 +1,7 @@
import { useEffect, RefObject } from 'react';
export function useClickAway(
ref: RefObject<HTMLElement>,
ref: RefObject<HTMLElement | null>,
handler: (event: MouseEvent | TouchEvent) => void
) {
useEffect(() => {

View file

@ -1,14 +1,20 @@
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge";
import { auth0 } from "./app/lib/auth0";
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, x-client-id, Authorization',
}
const auth0MiddlewareHandler = withMiddlewareAuthRequired();
export async function middleware(request: NextRequest, event: NextFetchEvent) {
const authRes = await auth0.middleware(request);
// Check if the request path starts with /api/auth/
if (request.nextUrl.pathname.startsWith('/auth')) {
return authRes;
}
// Check if the request path starts with /api/
if (request.nextUrl.pathname.startsWith('/api/')) {
// Handle preflighted requests
@ -37,10 +43,9 @@ export async function middleware(request: NextRequest, event: NextFetchEvent) {
request.nextUrl.pathname.startsWith('/billing') ||
request.nextUrl.pathname.startsWith('/onboarding')) {
// Skip auth check if USE_AUTH is not enabled
if (process.env.USE_AUTH !== 'true') {
return NextResponse.next();
if (process.env.USE_AUTH === 'true') {
return authRes;
}
return auth0MiddlewareHandler(request, event);
}
return NextResponse.next();
@ -48,10 +53,12 @@ export async function middleware(request: NextRequest, event: NextFetchEvent) {
export const config = {
matcher: [
'/projects/:path*',
'/billing/:path*',
// '/onboarding/:path*',
'/api/v1/:path*',
'/api/widget/v1/:path*',
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
],
};
};

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
@ -16,7 +16,7 @@
},
"dependencies": {
"@ai-sdk/openai": "^1.3.21",
"@auth0/nextjs-auth0": "^3.5.0",
"@auth0/nextjs-auth0": "^4.7.0",
"@aws-sdk/client-s3": "^3.743.0",
"@aws-sdk/s3-request-presigner": "^3.743.0",
"@dnd-kit/core": "^6.3.1",
@ -25,14 +25,14 @@
"@floating-ui/react": "^0.27.7",
"@google/generative-ai": "^0.21.0",
"@heroicons/react": "^2.2.0",
"@heroui/react": "2.7.4",
"@heroui/system": "2.4.11",
"@heroui/theme": "2.4.11",
"@heroui/react": "^2.8.0-beta.10",
"@heroui/system": "^2.4.18-beta.2",
"@heroui/theme": "^2.4.18-beta.2",
"@langchain/core": "^0.3.7",
"@langchain/textsplitters": "^0.1.0",
"@mendable/firecrawl-js": "^1.0.3",
"@modelcontextprotocol/sdk": "^1.12.1",
"@primer/react": "^36.27.0",
"@primer/react": "^37.27.0",
"@qdrant/js-client-rest": "^1.13.0",
"ai": "^4.3.13",
"cheerio": "^1.0.0",
@ -41,24 +41,24 @@
"date-fns": "^4.1.0",
"dotenv": "^16.4.5",
"eventsource-parser": "^3.0.2",
"framer-motion": "^11.5.4",
"framer-motion": "^12.19.1",
"fuse.js": "^7.1.0",
"immer": "^10.1.1",
"jose": "^5.9.6",
"lucide-react": "^0.465.0",
"mongodb": "^6.8.0",
"next": "^14.2.25",
"next": "15.3.4",
"openai": "^4.67.2",
"quill": "^2.0.3",
"quill-mention": "^6.0.2",
"react": "^18.3.1",
"react-diff-viewer-continued": "^3.4.0",
"react-dom": "^18.3.1",
"react": "19.1.0",
"react-diff-viewer-continued": "^4.0.6",
"react-dom": "19.1.0",
"react-dropzone": "^14.3.5",
"react-markdown": "^9.0.1",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^2.1.7",
"redis": "^4.7.0",
"remark-gfm": "^4.0.0",
"remark-gfm": "^4.0.1",
"rowboat-shared": "github:rowboatlabs/shared",
"sharp": "^0.33.4",
"styled-components": "^5.3.11",
@ -67,20 +67,24 @@
"tailwindcss-animate": "^1.0.7",
"tiktoken": "^1.0.17",
"twilio": "^5.4.5",
"typewriter-effect": "^2.21.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@types/redis": "^4.0.11",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint-config-next": "15.3.4",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.10",
"tsx": "^4.19.1",
"typescript": "^5"
},
"overrides": {
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6"
}
}

View file

@ -1,8 +1,6 @@
/** @type {import('postcss-load-config').Config} */
const config = {
/** @type {import('tailwindcss').Config} */
export default {
plugins: {
tailwindcss: {},
'@tailwindcss/postcss': {},
},
};
export default config;
}

View file

@ -1,108 +0,0 @@
import { heroui } from "@heroui/theme";
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@heroui/theme/dist/components/[object Object].js",
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
keyframes: {
shine: {
'100%': { transform: 'translateX(200%)' }
},
'pulse-subtle': {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.85' }
},
gradient: {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' }
},
'sparkle-fade': {
'0%': { opacity: '0.2', transform: 'scale(0.9)' },
'50%': { opacity: '0.5', transform: 'scale(1.1)' },
'100%': { opacity: '0.2', transform: 'scale(0.9)' }
},
typing: {
'0%, 5%': { width: '0%' },
'45%, 55%': { width: '100%' },
'95%, 100%': { width: '0%' }
},
blink: {
'50%': { borderColor: 'transparent' }
}
},
animation: {
shine: 'shine 2s infinite',
'pulse-subtle': 'pulse-subtle 2s infinite',
'gradient': 'gradient var(--gradient-animation-duration, 15s) ease infinite',
'sparkle': 'sparkle-fade 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'typing': 'typing 8s cubic-bezier(0.4, 0, 0.2, 1) infinite',
'cursor': 'blink .75s step-end infinite'
},
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
}
}
},
plugins: [
heroui(),
require("tailwindcss-animate")
],
};
export default config;

View file

@ -1,6 +1,10 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -18,9 +22,19 @@
}
],
"paths": {
"@/*": ["./*"]
}
"@/*": [
"./*"
]
},
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}