Merge pull request #168 from rowboatlabs/upgrade

Upgrade to Next.js 15
This commit is contained in:
Ramnique Singh 2025-06-24 15:49:43 +05:30 committed by GitHub
commit 11a3a2f357
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 5344 additions and 11135 deletions

View file

@ -1,5 +1,5 @@
"use server"; "use server";
import { getSession } from "@auth0/nextjs-auth0"; import { auth0 } from "../lib/auth0";
import { USE_AUTH } from "../lib/feature_flags"; import { USE_AUTH } from "../lib/feature_flags";
import { WithStringId, User } from "../lib/types/types"; import { WithStringId, User } from "../lib/types/types";
import { getUserFromSessionId, GUEST_DB_USER } from "../lib/auth"; 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; return GUEST_DB_USER;
} }
const { user } = await getSession() || {}; const { user } = await auth0.getSession() || {};
if (!user) { if (!user) {
throw new Error('User not authenticated'); throw new Error('User not authenticated');
} }

View file

@ -777,7 +777,7 @@ export async function generateServerAuthUrl(
await projectAuthCheck(projectId); await projectAuthCheck(projectId);
// Get the origin from request headers // Get the origin from request headers
const headersList = headers(); const headersList = await headers();
const host = headersList.get('host') || ''; const host = headersList.get('host') || '';
const protocol = headersList.get('x-forwarded-proto') || 'http'; const protocol = headersList.get('x-forwarded-proto') || 'http';
const origin = `${protocol}://${host}`; 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 { redisClient } from "@/app/lib/redis";
import { CopilotAPIRequest } from "@/app/lib/types/copilot_types"; 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 // get the payload from redis
const payload = await redisClient.get(`copilot-stream-${params.streamId}`); const payload = await redisClient.get(`copilot-stream-${params.streamId}`);
if (!payload) { 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 { AgenticAPIChatMessage, AgenticAPIChatRequest, convertFromAgenticAPIChatMessages } from "@/app/lib/types/agents_api_types";
import { createParser, type EventSourceMessage } from 'eventsource-parser'; 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 // get the payload from redis
const payload = await redisClient.get(`chat-stream-${params.streamId}`); const payload = await redisClient.get(`chat-stream-${params.streamId}`);
if (!payload) { if (!payload) {

View file

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

View file

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

View file

@ -1,23 +1,21 @@
'use client'; 'use client';
import { TypewriterEffect } from "./lib/components/typewriter";
import Image from 'next/image'; import Image from 'next/image';
import logo from "@/public/logo.png"; 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 { useRouter } from "next/navigation";
import { Spinner } from "@heroui/react"; import { Spinner } from "@heroui/react";
import { LogInIcon } from "lucide-react";
export function App() { export function App() {
const router = useRouter(); const router = useRouter();
const { user, error, isLoading } = useUser(); const { user, isLoading } = useUser();
if (user) { if (user) {
router.push("/projects"); router.push("/projects");
} }
// Add auto-redirect for non-authenticated users // Add auto-redirect for non-authenticated users
if (!isLoading && !user && !error) { if (!isLoading && !user) {
router.push("/api/auth/login"); router.push("/auth/login");
} }
return ( return (
@ -30,8 +28,7 @@ export function App() {
alt="RowBoat Logo" alt="RowBoat Logo"
height={40} height={40}
/> />
{(isLoading || (!user && !error)) && <Spinner size="sm" />} {(isLoading || !user) && <Spinner size="sm" />}
{error && <div className="text-red-500">{error.message}</div>}
{user && <div className="flex items-center gap-2"> {user && <div className="flex items-center gap-2">
<Spinner size="sm" /> <Spinner size="sm" />
<div className="text-sm text-gray-400">Welcome, {user.name}</div> <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 const dynamic = 'force-dynamic';
export default async function Page({ export default async function Page(
searchParams, props: {
}: { searchParams: Promise<{
searchParams: {
redirect: string; redirect: string;
}>
} }
}) { ) {
const searchParams = await props.searchParams;
const customer = await requireBillingCustomer(); const customer = await requireBillingCustomer();
await syncWithStripe(customer._id); await syncWithStripe(customer._id);
const redirectUrl = searchParams.redirect as string; 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'; @import './styles/quill-mentions.css';
@tailwind base;
@tailwind components; @plugin './hero.ts';
@tailwind utilities;
@source '../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
@custom-variant dark (&:is(.dark *));
@reference 'tailwindcss';
@layer utilities { @layer utilities {
.text-balance { .text-balance {
@ -106,74 +112,16 @@ html, body {
input, textarea, select { input, textarea, select {
@apply rounded-lg border-[#E5E7EB] dark:border-[#2E2E30] @apply rounded-lg border-[#E5E7EB] dark:border-[#2E2E30]
bg-[#F3F4F6] dark:bg-[#2A2A2D] 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; 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 { html {
font-family: 'Inter', system-ui, -apple-system, sans-serif; font-family: 'Inter', system-ui, -apple-system, sans-serif;
} }
@keyframes slideUpAndFade { body {
from { background: var(--background);
opacity: 0; color: var(--foreground);
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.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 "./globals.css";
import { ThemeProvider } from "./providers/theme-provider"; import { ThemeProvider } from "./providers/theme-provider";
import { UserProvider } from '@auth0/nextjs-auth0/client';
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { Providers } from "./providers"; import { Providers } from "./providers";
import { Metadata } from "next"; import { Metadata } from "next";
import { HelpModalProvider } from "./providers/help-modal-provider"; import { HelpModalProvider } from "./providers/help-modal-provider";
import { Auth0Provider } from "@auth0/nextjs-auth0";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -21,7 +21,7 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return <html lang="en" className="h-dvh"> return <html lang="en" className="h-dvh">
<UserProvider> <Auth0Provider>
<ThemeProvider> <ThemeProvider>
<body className={`${inter.className} h-full text-base [scrollbar-width:thin] bg-background`}> <body className={`${inter.className} h-full text-base [scrollbar-width:thin] bg-background`}>
<Providers className='h-full flex flex-col'> <Providers className='h-full flex flex-col'>
@ -31,6 +31,6 @@ export default function RootLayout({
</Providers> </Providers>
</body> </body>
</ThemeProvider> </ThemeProvider>
</UserProvider> </Auth0Provider>
</html>; </html>;
} }

View file

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

View file

@ -9,8 +9,8 @@ export default function MarkdownContent({
content: string; content: string;
atValues?: Match[]; atValues?: Match[];
}) { }) {
return <Markdown return <div className="overflow-auto break-words">
className="overflow-auto break-words" <Markdown
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
components={{ components={{
h1({ children }) { h1({ children }) {
@ -107,5 +107,6 @@ export default function MarkdownContent({
}} }}
> >
{content} {content}
</Markdown>; </Markdown>
</div>;
} }

View file

@ -1,3 +1,5 @@
@import "../../globals.css";
/* Target both edit mode and view mode mentions */ /* Target both edit mode and view mode mentions */
.mention, .mention,
.ql-editor p span[class*="bg-[#e"], /* Matches both #e8f2fe and #e0f2fe */ .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 */ /* 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) .ql-editor p span[class*="bg-[#e"],
:global(.dark) span[class*="bg-[#e"] { :global(.dark) span[class*="bg-[#e"] {
background-color: rgb(31 41 55) !important; background-color: rgb(31 41 55) !important;
color: rgb(243 244 246) !important; color: rgb(243 244 246) !important;
} } */
/* Override the inline styles */ /* Override the inline styles */
.ql-editor p span[class*="bg-[#e0f2fe]"], .ql-editor p span[class*="bg-[#e0f2fe]"],

View file

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

View file

@ -1,5 +1,5 @@
'use client'; '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 { Avatar, Dropdown, DropdownItem, DropdownSection, DropdownTrigger, DropdownMenu } from "@heroui/react";
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,15 @@
import { SourcePage } from "./source-page"; import { SourcePage } from "./source-page";
import { requireActiveBillingSubscription } from '@/app/lib/billing'; import { requireActiveBillingSubscription } from '@/app/lib/billing';
export default async function Page({ export default async function Page(
params, props: {
}: { params: Promise<{
params: {
projectId: string, projectId: string,
sourceId: string sourceId: string
}>
} }
}) { ) {
const params = await props.params;
await requireActiveBillingSubscription(); await requireActiveBillingSubscription();
return <SourcePage projectId={params.projectId} sourceId={params.sourceId} />; 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 }) { export function SectionLabel({ children, className }: { children: ReactNode; className?: string }) {
return ( 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} {children}
</div> </div>
); );

View file

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

View file

@ -36,7 +36,7 @@ export function ToggleSource({
onClick={handleToggle} onClick={handleToggle}
disabled={loading} disabled={loading}
className={` 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 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'} ${isActive ? 'bg-indigo-500' : 'bg-gray-200 dark:bg-gray-700'}
disabled:opacity-50 disabled:cursor-not-allowed disabled:opacity-50 disabled:cursor-not-allowed

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,8 @@ import { RunsApp } from "./runs_app";
import { TestingMenu } from "./testing_menu"; import { TestingMenu } from "./testing_menu";
import { requireActiveBillingSubscription } from '@/app/lib/billing'; 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(); await requireActiveBillingSubscription();
const { projectId, slug = [] } = params; const { projectId, slug = [] } = params;
let app: "scenarios" | "simulations" | "profiles" | "runs" = "runs"; let app: "scenarios" | "simulations" | "profiles" | "runs" = "runs";

View file

@ -295,7 +295,7 @@ export function CustomServers() {
<div className="space-y-6"> <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="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 gap-3">
<div className="flex-shrink-0"> <div className="shrink-0">
<Info className="h-5 w-5 text-blue-600 dark:text-blue-400" /> <Info className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div> </div>
<p className="text-sm text-blue-700 dark:text-blue-300"> <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="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="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 gap-3">
<div className="flex-shrink-0"> <div className="shrink-0">
<Info className="h-5 w-5 text-blue-600 dark:text-blue-400" /> <Info className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div> </div>
<p className="text-sm text-blue-700 dark:text-blue-300"> <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 || ''} value={item || ''}
onChange={(e) => handleArrayItemChange(index, e.target.value)} onChange={(e) => handleArrayItemChange(index, e.target.value)}
placeholder="Enter 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' ? ( ) : itemSchema.type === 'number' || itemSchema.type === 'integer' ? (
<Input <Input
@ -209,7 +209,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
handleArrayItemChange(index, isNaN(val) ? '' : val); handleArrayItemChange(index, isNaN(val) ? '' : val);
}} }}
placeholder="Enter 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 === 'boolean' ? ( ) : itemSchema.type === 'boolean' ? (
<div className="scale-75 origin-left"> <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 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 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: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> <option value="" disabled>Select {paramName}</option>
{schema.enum.map((opt: string) => ( {schema.enum.map((opt: string) => (
@ -299,7 +299,7 @@ export function TestToolModal({ isOpen, onClose, tool, server }: TestToolModalPr
type="datetime-local" type="datetime-local"
value={value} value={value}
onChange={(e) => handleParameterChange(paramName, e.target.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" type="date"
value={value} value={value}
onChange={(e) => handleParameterChange(paramName, e.target.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" type="time"
value={value} value={value}
onChange={(e) => handleParameterChange(paramName, e.target.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} value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)} onChange={(e) => handleParameterChange(paramName, e.target.value)}
placeholder={`Enter ${paramName}`} 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); handleParameterChange(paramName, isNaN(val) ? '' : val);
}} }}
placeholder={`Enter ${paramName}`} 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} value={value}
onChange={(e) => handleParameterChange(paramName, e.target.value)} onChange={(e) => handleParameterChange(paramName, e.target.value)}
placeholder={`Enter ${paramName}`} 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={ <Tab key="hosted" title={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span>Tools Library</span> <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 BETA
</span> </span>
</div> </div>

View file

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

View file

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

View file

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

View file

@ -148,7 +148,7 @@ function PreviewModal({
</div>} </div>}
{view === 'markdown' && <div className="flex gap-1"> {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"> {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"> <div className="p-2 overflow-auto">
<MarkdownContent <MarkdownContent
content={oldValue} content={oldValue}
@ -158,7 +158,7 @@ function PreviewModal({
<div className={clsx("flex flex-col", { <div className={clsx("flex flex-col", {
'w-1/2': oldValue !== undefined '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"> <div className="p-2 overflow-auto">
<MarkdownContent <MarkdownContent
content={newValue} content={newValue}

View file

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

View file

@ -1,26 +1,20 @@
import Link from "next/link";
import { LucideIcon } from "lucide-react"; import { LucideIcon } from "lucide-react";
interface MenuItemProps { interface MenuItemProps {
href?: string;
icon: LucideIcon; icon: LucideIcon;
selected?: boolean; selected?: boolean;
collapsed?: boolean; collapsed?: boolean;
onClick?: () => void;
children?: React.ReactNode; children?: React.ReactNode;
} }
export default function MenuItem({ export default function MenuItem({
href,
icon: Icon, icon: Icon,
selected = false, selected = false,
collapsed = false, collapsed = false,
onClick,
children children
}: MenuItemProps) { }: MenuItemProps) {
const ButtonContent = ( return (
<button <div
onClick={onClick}
className={` className={`
w-full px-3 py-2 rounded-md flex items-center gap-3 w-full px-3 py-2 rounded-md flex items-center gap-3
text-sm font-medium transition-all duration-200 text-sm font-medium transition-all duration-200
@ -32,12 +26,6 @@ export default function MenuItem({
> >
<Icon size={16} /> <Icon size={16} />
{!collapsed && children} {!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 ( return (
<> <>
<aside className={`${collapsed ? 'w-16' : 'w-60'} bg-transparent flex flex-col h-full transition-all duration-300`}> <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 && ( {!isProjectsRoute && (
<> <>
{/* Project Selector */} {/* Project Selector */}
@ -141,9 +141,6 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
> >
<Link <Link
href={isDisabled ? '#' : fullPath} href={isDisabled ? '#' : fullPath}
className={isDisabled ? 'pointer-events-none' : ''}
>
<button
className={` className={`
relative w-full rounded-md flex items-center relative w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200 text-[15px] font-medium transition-all duration-200
@ -154,8 +151,8 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
? 'text-zinc-300 dark:text-zinc-600 cursor-not-allowed' ? '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' : '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' : ''}
`} `}
disabled={isDisabled}
data-tour-target={item.href === 'config' ? 'settings' : item.href === 'sources' ? 'entity-data-sources' : undefined} data-tour-target={item.href === 'config' ? 'settings' : item.href === 'sources' ? 'entity-data-sources' : undefined}
> >
<Icon <Icon
@ -174,13 +171,12 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
<> <>
<span>{item.label}</span> <span>{item.label}</span>
{item.beta && ( {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"> <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 BETA
</span> </span>
)} )}
</> </>
)} )}
</button>
</Link> </Link>
</Tooltip> </Tooltip>
); );

View file

@ -29,7 +29,7 @@ export function Nav({
setCollapsed(!collapsed); 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-40": !collapsed,
"w-10": collapsed "w-10": collapsed
})}> })}>

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ export function HelpModal({ isOpen, onClose, onStartTour }: HelpModalProps) {
if (!isOpen) return null; if (!isOpen) return null;
return ( 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"> <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"> <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">
Need Help? Need Help?

View file

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

View file

@ -89,7 +89,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
return ( return (
<> <>
{/* Top */} {/* 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, top: 0,
left: 0, left: 0,
right: 0, right: 0,
@ -97,7 +97,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
}} /> }} />
{/* Left */} {/* 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), top: Math.max(0, rect.top - padding),
left: 0, left: 0,
width: Math.max(0, rect.left - padding), width: Math.max(0, rect.left - padding),
@ -105,7 +105,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
}} /> }} />
{/* Right */} {/* 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), top: Math.max(0, rect.top - padding),
left: rect.right + padding, left: rect.right + padding,
right: 0, right: 0,
@ -113,7 +113,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
}} /> }} />
{/* Bottom */} {/* 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, top: rect.bottom + padding,
left: 0, left: 0,
right: 0, right: 0,
@ -122,7 +122,7 @@ function TourBackdrop({ targetElement }: { targetElement: Element | null }) {
{/* Highlight border around target */} {/* Highlight border around target */}
<div <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={{ style={{
top: rect.top - padding, top: rect.top - padding,
left: rect.left - padding, left: rect.left - padding,

View file

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

View file

@ -1,14 +1,20 @@
import { NextFetchEvent, NextRequest, NextResponse } from "next/server"; import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge"; import { auth0 } from "./app/lib/auth0";
const corsOptions = { const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, x-client-id, Authorization', 'Access-Control-Allow-Headers': 'Content-Type, x-client-id, Authorization',
} }
const auth0MiddlewareHandler = withMiddlewareAuthRequired();
export async function middleware(request: NextRequest, event: NextFetchEvent) { 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/ // Check if the request path starts with /api/
if (request.nextUrl.pathname.startsWith('/api/')) { if (request.nextUrl.pathname.startsWith('/api/')) {
// Handle preflighted requests // Handle preflighted requests
@ -37,10 +43,9 @@ export async function middleware(request: NextRequest, event: NextFetchEvent) {
request.nextUrl.pathname.startsWith('/billing') || request.nextUrl.pathname.startsWith('/billing') ||
request.nextUrl.pathname.startsWith('/onboarding')) { request.nextUrl.pathname.startsWith('/onboarding')) {
// Skip auth check if USE_AUTH is not enabled // Skip auth check if USE_AUTH is not enabled
if (process.env.USE_AUTH !== 'true') { if (process.env.USE_AUTH === 'true') {
return NextResponse.next(); return authRes;
} }
return auth0MiddlewareHandler(request, event);
} }
return NextResponse.next(); return NextResponse.next();
@ -48,10 +53,12 @@ export async function middleware(request: NextRequest, event: NextFetchEvent) {
export const config = { export const config = {
matcher: [ matcher: [
'/projects/:path*', /*
'/billing/:path*', * Match all request paths except for the ones starting with:
// '/onboarding/:path*', * - _next/static (static files)
'/api/v1/:path*', * - _next/image (image optimization files)
'/api/widget/v1/:path*', * - 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, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
@ -16,7 +16,7 @@
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^1.3.21", "@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/client-s3": "^3.743.0",
"@aws-sdk/s3-request-presigner": "^3.743.0", "@aws-sdk/s3-request-presigner": "^3.743.0",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
@ -25,14 +25,14 @@
"@floating-ui/react": "^0.27.7", "@floating-ui/react": "^0.27.7",
"@google/generative-ai": "^0.21.0", "@google/generative-ai": "^0.21.0",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"@heroui/react": "2.7.4", "@heroui/react": "^2.8.0-beta.10",
"@heroui/system": "2.4.11", "@heroui/system": "^2.4.18-beta.2",
"@heroui/theme": "2.4.11", "@heroui/theme": "^2.4.18-beta.2",
"@langchain/core": "^0.3.7", "@langchain/core": "^0.3.7",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@mendable/firecrawl-js": "^1.0.3", "@mendable/firecrawl-js": "^1.0.3",
"@modelcontextprotocol/sdk": "^1.12.1", "@modelcontextprotocol/sdk": "^1.12.1",
"@primer/react": "^36.27.0", "@primer/react": "^37.27.0",
"@qdrant/js-client-rest": "^1.13.0", "@qdrant/js-client-rest": "^1.13.0",
"ai": "^4.3.13", "ai": "^4.3.13",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
@ -41,24 +41,24 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"eventsource-parser": "^3.0.2", "eventsource-parser": "^3.0.2",
"framer-motion": "^11.5.4", "framer-motion": "^12.19.1",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"immer": "^10.1.1", "immer": "^10.1.1",
"jose": "^5.9.6", "jose": "^5.9.6",
"lucide-react": "^0.465.0", "lucide-react": "^0.465.0",
"mongodb": "^6.8.0", "mongodb": "^6.8.0",
"next": "^14.2.25", "next": "15.3.4",
"openai": "^4.67.2", "openai": "^4.67.2",
"quill": "^2.0.3", "quill": "^2.0.3",
"quill-mention": "^6.0.2", "quill-mention": "^6.0.2",
"react": "^18.3.1", "react": "19.1.0",
"react-diff-viewer-continued": "^3.4.0", "react-diff-viewer-continued": "^4.0.6",
"react-dom": "^18.3.1", "react-dom": "19.1.0",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-markdown": "^9.0.1", "react-markdown": "^10.1.0",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"redis": "^4.7.0", "redis": "^4.7.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.1",
"rowboat-shared": "github:rowboatlabs/shared", "rowboat-shared": "github:rowboatlabs/shared",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"styled-components": "^5.3.11", "styled-components": "^5.3.11",
@ -67,20 +67,24 @@
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tiktoken": "^1.0.17", "tiktoken": "^1.0.17",
"twilio": "^5.4.5", "twilio": "^5.4.5",
"typewriter-effect": "^2.21.0",
"zod": "^3.23.8", "zod": "^3.23.8",
"zod-to-json-schema": "^3.23.5" "zod-to-json-schema": "^3.23.5"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "19.1.8",
"@types/react-dom": "^18", "@types/react-dom": "19.1.6",
"@types/redis": "^4.0.11", "@types/redis": "^4.0.11",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.5", "eslint-config-next": "15.3.4",
"postcss": "^8", "postcss": "^8.5.6",
"tailwindcss": "^3.4.1", "tailwindcss": "^4.1.10",
"tsx": "^4.19.1", "tsx": "^4.19.1",
"typescript": "^5" "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} */ /** @type {import('tailwindcss').Config} */
const config = { export default {
plugins: { 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": { "compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -18,9 +22,19 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": [
} "./*"
]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "target": "ES2017"
"exclude": ["node_modules"] },
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }