Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add onboarding ai agent #86

Merged
merged 11 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"dev": "next dev -p 3001 --turbopack",
"prebuild": "turbo --filter=@repo/db db:generate",
"build": "next build",
"start": "next start -p 5555",
"preview": "next build && next start -p 5555",
"start": "next start -p 3001",
"preview": "next build && next start -p 3001",
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Documentation update needed for port 3001

The port change to 3001 in the API service requires documentation updates. Our verification found that:

  • No port references exist in any README files
  • No environment templates are present
  • No port configurations in Docker files or config files
  • No explicit port documentation in any of the existing documentation

Please update the following:

  • Add port information to apps/api/README.md
  • Create an .env.example file in the API service directory
  • Update the main README.md with the development ports for all services
  • Document port configurations in Docker setup if containerization is used
🔗 Analysis chain

Documentation update needed for port change.

Since this is a significant infrastructure change that affects local development and deployment, documentation should be updated to reflect the new port.

Please ensure the following are updated:

  • README files
  • Development setup guides
  • Deployment documentation
  • Environment variable templates

Let's check for documentation files that might need updating:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for documentation files
fd -e md -e mdx

# Look for environment templates
fd -g '.env.example' -g '.env.template'

Length of output: 294


Script:

#!/bin/bash
# Check README files for port mentions
echo "=== Checking main README.md ==="
rg -i "port|3001|3000" README.md || echo "No port mentions found"

echo -e "\n=== Checking API README ==="
rg -i "port|3001|3000" apps/api/README.md || echo "No port mentions found"

echo -e "\n=== Checking other documentation for port references ==="
rg -i "port|3001|3000" CONTRIBUTING.md || echo "No port mentions found"

echo -e "\n=== Checking for environment files ==="
fd ".env" --hidden --no-ignore

Length of output: 7840


Script:

#!/bin/bash
# Let's check for any docker or docker-compose files that might reference the port
echo "=== Checking Docker files for port references ==="
fd -e dockerfile -e yml -e yaml | xargs rg -i "port.*3001|port.*3000" || echo "No port mentions found in Docker files"

# Check for any configuration files that might contain port references
echo -e "\n=== Checking common config files ==="
fd -e json -e js -e ts -g "config*" | xargs rg -i "port.*3001|port.*3000" || echo "No port mentions found in config files"

# Check specifically for Next.js config files
echo -e "\n=== Checking Next.js config files ==="
fd "next.config" | xargs rg -i "port" || echo "No port mentions found in Next.js config"

Length of output: 1115

"lint": "next lint",
"lint:fix": "next lint --fix",
"test": "vitest --run",
Expand Down
50 changes: 50 additions & 0 deletions apps/app/actions/action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use server";
import { ReactNode } from "react";
import { getMutableAIState, streamUI } from "ai/rsc";
import { togetherai } from "@ai-sdk/togetherai";
import { AI } from "@/lib/ai";
import { BotMessage } from "@/components/custom/onboarding/message";

export type ServerMessage = {
role: "user" | "assistant";
content: string;
};

export type ClientMessage = {
id: number;
role: "user" | "assistant";
display: ReactNode;
};

export const sendMessage = async (
message: string
): Promise<{
id: number;
role: "user" | "assistant";
display: ReactNode;
}> => {
const history = getMutableAIState<typeof AI>();
history.update([...history.get(), { role: "user", content: message }]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement proper state management for message history

The current implementation might lead to memory issues as the history grows indefinitely.

Consider:

  1. Implementing a maximum history size
  2. Cleaning up old messages
  3. Using a more robust state management solution
+ const MAX_HISTORY_SIZE = 50;
  const history = getMutableAIState<typeof AI>();
- history.update([...history.get(), { role: "user", content: message }]);
+ const currentHistory = history.get();
+ const newHistory = [...currentHistory.slice(-MAX_HISTORY_SIZE), { role: "user", content: message }];
+ history.update(newHistory);

Also applies to: 34-39

const response = await streamUI({
model: togetherai("deepseek-ai/deepseek-llm-67b-chat"),
system: "You are a helpful assistant. give answer in only 10 words",
messages: [{ role: "user", content: message }, ...history.get()],
text: ({ content, done }) => {
if (done) {
history.update([
...history.get(),
{ role: "assistant", content: content },
]);
console.log("done", ...history.get());
}
return <BotMessage>{content}</BotMessage>;
},
});


return {
id: Date.now(),
role: "assistant" as const,
display: response.value,
};
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and input validation

The function lacks error handling for API failures and input validation.

Consider implementing:

  1. Input validation for empty messages
  2. Error handling for API calls
  3. Timeout handling for streaming
 export const sendMessage = async (
   message: string
 ): Promise<ClientMessage> => {
+  if (!message?.trim()) {
+    throw new Error('Message cannot be empty');
+  }
+
   const history = getMutableAIState<typeof AI>();
   try {
     // ... existing code ...
+  } catch (error) {
+    console.error('Error in AI response:', error);
+    throw new Error('Failed to get AI response');
+  }
 };

Committable suggestion skipped: line range outside the PR's diff.

5 changes: 5 additions & 0 deletions apps/app/app/(onboarding)/onboarding/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AI } from "@/lib/ai";

export default function OnboardingLayout({children}: {children: React.ReactNode}) {
return <AI>{children}</AI>;
}
6 changes: 6 additions & 0 deletions apps/app/app/(onboarding)/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Chatbox from "@/components/custom/onboarding/chatbox";

export default function Onboarding() {

return <Chatbox />
}
20 changes: 20 additions & 0 deletions apps/app/app/(routes)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Infobar from "@/components/custom/infobar/infobar";
import ProgressBar from "@/components/custom/progress.bar";
import { AppSidebar } from "@/components/custom/sidebar/sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";
import { cookies } from "next/headers";
async function RouteLayout({children}: {children: React.ReactNode}) {
const cookieStore = await cookies();
const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true";
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for cookie access

The cookie access should handle potential errors and provide fallback behavior.

-  const cookieStore = await cookies();
-  const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true";
+  const defaultOpen = await (async () => {
+    try {
+      const cookieStore = await cookies();
+      return cookieStore.get("plura-sidebar:state")?.value === "true";
+    } catch (error) {
+      console.error('Failed to access cookies:', error);
+      return false;
+    }
+  })();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cookieStore = await cookies();
const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true";
const defaultOpen = await (async () => {
try {
const cookieStore = await cookies();
return cookieStore.get("plura-sidebar:state")?.value === "true";
} catch (error) {
console.error('Failed to access cookies:', error);
return false;
}
})();

return (
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<div className="p-2">
<ProgressBar />
<Infobar />
{children}
</div>
</SidebarProvider>
);
}
export default RouteLayout;
45 changes: 15 additions & 30 deletions apps/app/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import type { Metadata } from "next";
import { GeistSans } from "geist/font/sans";
import "./globals.css";
import { ThemeProvider } from "@/hooks/theme-provider";
import { SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/custom/sidebar/sidebar";
import { cookies } from "next/headers";
import Infobar from "@/components/custom/infobar/infobar";
import ProgressBar from "@/components/custom/progress.bar";
import { PosthogProvider } from "@/hooks/posthog";

export const metadata: Metadata = {
title: "Plura",
description: "Generated by create next app",
Expand All @@ -19,33 +13,24 @@ async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true";

return (
<html lang="en" suppressHydrationWarning>
<PosthogProvider>
<body
className={`min-h-screen bg-background font-sans ${GeistSans.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
<html lang="en" suppressHydrationWarning>
<PosthogProvider>
<body
className={`min-h-screen bg-background font-sans ${GeistSans.variable} antialiased`}
>
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<div className="p-2">
<ProgressBar />
<Infobar />
{children}
</div>
</SidebarProvider>
</ThemeProvider>
</body>
</PosthogProvider>
</html>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</PosthogProvider>
</html>
);
}

Expand Down
15 changes: 15 additions & 0 deletions apps/app/components/custom/onboarding/chatList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { UIState } from "@/lib/ai";

export function ChatList({ messages }: { messages: UIState[number][] }) {
if (!messages.length) return null;

return (
<div className="relative mx-auto max-w-2xl px-4">
{messages.map((message, index) => (
<div key={index} className="pb-4">
{message.display}
</div>
))}
Comment on lines +9 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using array indices as React keys

Using array indices as keys can lead to rendering issues when messages are reordered, deleted, or inserted. Consider using unique message IDs instead.

-      {messages.map((message, index) => (
-        <div key={index} className="pb-4">
+      {messages.map((message) => (
+        <div key={message.id} className="pb-4">

Committable suggestion skipped: line range outside the PR's diff.

</div>
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility and message differentiation

The chat messages lack semantic structure and visual differentiation between user and AI messages.

   return (
-    <div className="relative mx-auto max-w-2xl px-4">
+    <div 
+      className="relative mx-auto max-w-2xl px-4"
+      role="log"
+      aria-label="Chat messages"
+    >
       {messages.map((message) => (
-        <div key={index} className="pb-4">
+        <article 
+          key={message.id} 
+          className={`pb-4 ${
+            message.role === 'assistant' 
+              ? 'bg-muted/50 p-4 rounded-lg' 
+              : ''
+          }`}
+        >
           {message.display}
-        </div>
+        </article>
       ))}
     </div>
   );

Committable suggestion skipped: line range outside the PR's diff.

}
34 changes: 34 additions & 0 deletions apps/app/components/custom/onboarding/chatbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client"

import { Input } from "@/components/ui/input";
import { AI } from "@/lib/ai";
import { useActions, useUIState } from "ai/rsc";
import { useState } from "react";
import { ChatList } from "./chatList";
import { UserMessage } from "./message";

export default function Chatbox() {
const [messages, setMessages] = useUIState<typeof AI>();
const [value, setValue] = useState("");
const { sendMessage } = useActions<typeof AI>();
const handleSubmit = async() => {
setMessages((currentMessages) => [
...currentMessages,
{
id: Date.now(),
role: "user",
display: <UserMessage>{value}</UserMessage>,
},
]);
const response = await sendMessage(value);
setMessages((currentMessages) => [...currentMessages, response]);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add loading state and error handling

The message submission lacks loading indicators and error handling, which could lead to poor user experience.

Consider implementing:

 export default function Chatbox() {
   const [messages, setMessages] = useUIState<typeof AI>();
   const [value, setValue] = useState("");
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
   const { sendMessage } = useActions<typeof AI>();
 
   const handleSubmit = async() => {
+    if (!value.trim()) return;
+    setError(null);
+    setIsLoading(true);
     try {
       setMessages((currentMessages) => [
         ...currentMessages,
         {
           id: Date.now(),
           role: "user",
           display: <UserMessage>{value}</UserMessage>,
         },
       ]);
       const response = await sendMessage(value);
       setMessages((currentMessages) => [...currentMessages, response]);
+      setValue('');
+    } catch (err) {
+      setError('Failed to send message. Please try again.');
+    } finally {
+      setIsLoading(false);
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = async() => {
setMessages((currentMessages) => [
...currentMessages,
{
id: Date.now(),
role: "user",
display: <UserMessage>{value}</UserMessage>,
},
]);
const response = await sendMessage(value);
setMessages((currentMessages) => [...currentMessages, response]);
}
const handleSubmit = async() => {
if (!value.trim()) return;
setError(null);
setIsLoading(true);
try {
setMessages((currentMessages) => [
...currentMessages,
{
id: Date.now(),
role: "user",
display: <UserMessage>{value}</UserMessage>,
},
]);
const response = await sendMessage(value);
setMessages((currentMessages) => [...currentMessages, response]);
setValue('');
} catch (err) {
setError('Failed to send message. Please try again.');
} finally {
setIsLoading(false);
}
}


return (
<div>
<ChatList messages={messages} />
<Input value={value} onChange={(e) => setValue(e.target.value)} />
<button onClick={handleSubmit}>submit</button>
</div>
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve accessibility and user experience

The current implementation lacks proper accessibility features and UX enhancements.

Consider these improvements:

   return (
-    <div>
+    <div role="region" aria-label="Chat interface">
       <ChatList messages={messages} />
-      <Input value={value} onChange={(e) => setValue(e.target.value)} />
-      <button onClick={handleSubmit}>submit</button>
+      <form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
+        <Input
+          value={value}
+          onChange={(e) => setValue(e.target.value)}
+          placeholder="Type your message..."
+          aria-label="Chat message"
+          disabled={isLoading}
+          onKeyPress={(e) => e.key === 'Enter' && handleSubmit()}
+        />
+        <button
+          type="submit"
+          disabled={isLoading || !value.trim()}
+          aria-busy={isLoading}
+        >
+          {isLoading ? 'Sending...' : 'Send'}
+        </button>
+      </form>
+      {error && <p role="alert" className="text-red-500">{error}</p>}
     </div>
   );

Committable suggestion skipped: line range outside the PR's diff.

}
57 changes: 57 additions & 0 deletions apps/app/components/custom/onboarding/message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { cn } from "@/lib/utils";
import { Sparkle, UserIcon } from "lucide-react";

// Different types of message bubbles.
export function UserMessage({ children }: { children: React.ReactNode }) {
return (
<div className="group relative flex items-start md:-ml-12">
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-background">
<UserIcon />
</div>
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
{children}
</div>
</div>
);
}

export function BotMessage({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("group relative flex items-start md:-ml-12", className)}>
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-primary text-primary-foreground">
<Sparkle />
</div>
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
{children}
</div>
</div>
);
}

export function BotCard({
children,
showAvatar = true,
}: {
children: React.ReactNode;
showAvatar?: boolean;
}) {
return (
<div className="group relative flex items-start md:-ml-12">
<div
className={cn(
"flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-primary text-primary-foreground",
!showAvatar && "invisible"
)}
>
<Sparkle />
</div>
<div className="ml-4 flex-1 px-1">{children}</div>
</div>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add accessibility attributes to message components.

Enhance accessibility by adding appropriate ARIA roles and labels.

 export function UserMessage({ children }: { children: React.ReactNode }) {
   return (
-    <div className="group relative flex items-start md:-ml-12">
+    <div 
+      role="log"
+      aria-label="User message"
+      className="group relative flex items-start md:-ml-12">
       {/* ... */}
     </div>
   );
 }

 export function BotMessage({
   children,
   className,
 }: {
   children: React.ReactNode;
   className?: string;
 }) {
   return (
-    <div className={cn("group relative flex items-start md:-ml-12", className)}>
+    <div
+      role="log"
+      aria-label="Assistant message"
+      className={cn("group relative flex items-start md:-ml-12", className)}>
       {/* ... */}
     </div>
   );
 }

 export function BotCard({
   children,
   showAvatar = true,
 }: {
   children: React.ReactNode;
   showAvatar?: boolean;
 }) {
   return (
-    <div className="group relative flex items-start md:-ml-12">
+    <div
+      role="log"
+      aria-label="Assistant card"
+      className="group relative flex items-start md:-ml-12">
       {/* ... */}
     </div>
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function UserMessage({ children }: { children: React.ReactNode }) {
return (
<div className="group relative flex items-start md:-ml-12">
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-background">
<UserIcon />
</div>
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
{children}
</div>
</div>
);
}
export function BotMessage({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("group relative flex items-start md:-ml-12", className)}>
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-primary text-primary-foreground">
<Sparkle />
</div>
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
{children}
</div>
</div>
);
}
export function BotCard({
children,
showAvatar = true,
}: {
children: React.ReactNode;
showAvatar?: boolean;
}) {
return (
<div className="group relative flex items-start md:-ml-12">
<div
className={cn(
"flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-primary text-primary-foreground",
!showAvatar && "invisible"
)}
>
<Sparkle />
</div>
<div className="ml-4 flex-1 px-1">{children}</div>
</div>
);
}
export function UserMessage({ children }: { children: React.ReactNode }) {
return (
<div
role="log"
aria-label="User message"
className="group relative flex items-start md:-ml-12">
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-background">
<UserIcon />
</div>
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
{children}
</div>
</div>
);
}
export function BotMessage({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
role="log"
aria-label="Assistant message"
className={cn("group relative flex items-start md:-ml-12", className)}>
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-primary text-primary-foreground">
<Sparkle />
</div>
<div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
{children}
</div>
</div>
);
}
export function BotCard({
children,
showAvatar = true,
}: {
children: React.ReactNode;
showAvatar?: boolean;
}) {
return (
<div
role="log"
aria-label="Assistant card"
className="group relative flex items-start md:-ml-12">
<div
className={cn(
"flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow-sm bg-primary text-primary-foreground",
!showAvatar && "invisible"
)}
>
<Sparkle />
</div>
<div className="ml-4 flex-1 px-1">{children}</div>
</div>
);
}

19 changes: 19 additions & 0 deletions apps/app/lib/ai.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createAI } from "ai/rsc";
import { ClientMessage, ServerMessage, sendMessage } from "@/actions/action";

export type AIState = ServerMessage[];
export type UIState = ClientMessage[];

export const AI = createAI<
AIState,
UIState,
{
sendMessage: (message: string) => Promise<ClientMessage>;
}
>({
initialAIState: [],
initialUIState: [],
actions: {
sendMessage,
},
});
14 changes: 13 additions & 1 deletion apps/app/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const redirectUrl =
process.env.NODE_ENV === "production"
? "https://www.plura.pro/auth"
: "http://localhost:3003/auth";
const appDomain = process.env.NODE_ENV === "production" ? "https://app.plura.pro" : "http://localhost:3002";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use environment variables for domain configuration

Hardcoding domains in the code makes it less maintainable and harder to configure.

-const appDomain = process.env.NODE_ENV === "production" ? "https://app.plura.pro" : "http://localhost:3002";
+const appDomain = process.env.NEXT_PUBLIC_APP_DOMAIN || (
+  process.env.NODE_ENV === "production" ? "https://app.plura.pro" : "http://localhost:3002"
+);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const appDomain = process.env.NODE_ENV === "production" ? "https://app.plura.pro" : "http://localhost:3002";
const appDomain = process.env.NEXT_PUBLIC_APP_DOMAIN || (
process.env.NODE_ENV === "production" ? "https://app.plura.pro" : "http://localhost:3002"
);


export default async function authMiddleware(request: NextRequest) {
const { data: session } = await betterFetch<Session>(
`${baseDomain}/api/auth/get-session`,
Expand All @@ -24,9 +26,19 @@ export default async function authMiddleware(request: NextRequest) {
console.log("redirecting to sign in");
return NextResponse.redirect(redirectUrl);
}
const currentPath = request.nextUrl.pathname;
Comment on lines 27 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for session fetch failures

The current implementation doesn't handle potential errors from the session fetch. Consider adding try-catch block to handle network failures or invalid responses gracefully.

-  if (!session) {
-    console.log("redirecting to sign in");
-    return NextResponse.redirect(redirectUrl);
-  }
+  try {
+    if (!session) {
+      return NextResponse.redirect(redirectUrl);
+    }
+  } catch (error) {
+    console.error('Session verification failed:', error);
+    return NextResponse.redirect(redirectUrl);
+  }

Committable suggestion skipped: line range outside the PR's diff.



if (!currentPath.startsWith("/onboarding") && !session.user.isOnboarding) {
return NextResponse.redirect(`${appDomain}/onboarding`);
}
if (currentPath.startsWith("/onboarding") && session.user.isOnboarding) {
return NextResponse.redirect(`${appDomain}/home`);
}
Comment on lines +33 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent potential redirect loops

The current implementation might cause redirect loops if the session state is inconsistent.

Add additional checks:

+  const MAX_REDIRECTS = 3;
+  const redirectCount = parseInt(request.headers.get('x-redirect-count') || '0');
+  
+  if (redirectCount >= MAX_REDIRECTS) {
+    return new NextResponse('Too many redirects', { status: 500 });
+  }
+
   if (!currentPath.startsWith("/onboarding") && !session.user.isOnboarding) {
-    return NextResponse.redirect(`${appDomain}/onboarding`);
+    const response = NextResponse.redirect(`${appDomain}/onboarding`);
+    response.headers.set('x-redirect-count', (redirectCount + 1).toString());
+    return response;
   }
   if (currentPath.startsWith("/onboarding") && session.user.isOnboarding) {
-    return NextResponse.redirect(`${appDomain}/home`);
+    const response = NextResponse.redirect(`${appDomain}/home`);
+    response.headers.set('x-redirect-count', (redirectCount + 1).toString());
+    return response;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!currentPath.startsWith("/onboarding") && !session.user.isOnboarding) {
return NextResponse.redirect(`${appDomain}/onboarding`);
}
if (currentPath.startsWith("/onboarding") && session.user.isOnboarding) {
return NextResponse.redirect(`${appDomain}/home`);
}
const MAX_REDIRECTS = 3;
const redirectCount = parseInt(request.headers.get('x-redirect-count') || '0');
if (redirectCount >= MAX_REDIRECTS) {
return new NextResponse('Too many redirects', { status: 500 });
}
if (!currentPath.startsWith("/onboarding") && !session.user.isOnboarding) {
const response = NextResponse.redirect(`${appDomain}/onboarding`);
response.headers.set('x-redirect-count', (redirectCount + 1).toString());
return response;
}
if (currentPath.startsWith("/onboarding") && session.user.isOnboarding) {
const response = NextResponse.redirect(`${appDomain}/home`);
response.headers.set('x-redirect-count', (redirectCount + 1).toString());
return response;
}


return NextResponse.next();
}

export const config = {
matcher: "/:path*",
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)']
};
3 changes: 3 additions & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache"
},
"dependencies": {
"@ai-sdk/google": "^1.0.4",
"@ai-sdk/togetherai": "^0.0.3",
Comment on lines +18 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add error handling for AI SDK integration

The implementation in action.tsx lacks error handling for the AI SDK operations. While the packages are at stable versions (@ai-sdk/google at 1.0.4) and early but functional version (@ai-sdk/togetherai at 0.0.3), the following issues need attention:

  • No try-catch blocks around the streamUI and togetherai operations
  • Missing error handling for potential API failures or rate limits
  • No fallback mechanism for failed AI responses
🔗 Analysis chain

Verify stability of AI SDK dependencies

The @ai-sdk/togetherai package is at a very early version (0.0.3), which might not be production-ready. Consider the following:

  • Verify the package's stability and production readiness
  • Check for known issues or limitations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check latest versions and weekly downloads for stability assessment
echo "Checking @ai-sdk/google package:"
curl -s https://registry.npmjs.org/@ai-sdk/google | jq '.["dist-tags"].latest, .versions[.["dist-tags"].latest].time'

echo -e "\nChecking @ai-sdk/togetherai package:"
curl -s https://registry.npmjs.org/@ai-sdk/togetherai | jq '.["dist-tags"].latest, .versions[.["dist-tags"].latest].time'

# Check for recent GitHub issues
gh api graphql -f query='
{
  repository(owner: "togetherai", name: "sdk") {
    issues(last: 5, states: OPEN) {
      nodes {
        title
        createdAt
        labels(first: 5) {
          nodes {
            name
          }
        }
      }
    }
  }
}'

Length of output: 965


Script:

#!/bin/bash
# Let's check if these AI SDK packages are being used in the codebase
# and how they are implemented to assess the risk

# Search for imports/usage of @ai-sdk/togetherai
echo "Usage of @ai-sdk/togetherai:"
rg "@ai-sdk/togetherai" -A 3

# Search for imports/usage of @ai-sdk/google
echo -e "\nUsage of @ai-sdk/google:"
rg "@ai-sdk/google" -A 3

# Check if there are any error handling or try-catch blocks around these SDKs
echo -e "\nError handling around AI SDKs:"
ast-grep --pattern 'try {
  $$$
  @ai-sdk
  $$$
} catch'

Length of output: 2360


Script:

#!/bin/bash
# Let's check the implementation in action.tsx to see how these SDKs are used
# and if there's proper error handling

echo "Checking AI SDK implementation in action.tsx:"
cat apps/app/actions/action.tsx

# Let's also check if there are any try-catch blocks in this file
# using a more general pattern
echo -e "\nChecking error handling in action.tsx:"
ast-grep --pattern 'try {
  $$$
} catch' apps/app/actions/action.tsx

Length of output: 1625

"@better-fetch/fetch": "^1.1.12",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-alert-dialog": "^1.1.2",
Expand All @@ -35,6 +37,7 @@
"@repo/db": "workspace:*",
"@repo/types": "workspace:*",
"@tabler/icons-react": "^3.21.0",
"ai": "^4.0.8",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/auth/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export const auth = betterAuth({
}),
secret: process.env.BETTER_AUTH_SECRET,
plugins: [multiSession()],
user: {
additionalFields:{
isOnboarding: {
type: "boolean",
nullable:false,
required:true,
input:false,
defaultValue: false
}
}
},
emailAndPassword: {
enabled: true,
autoSignIn: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "user" ADD COLUMN "isOnboarding" BOOLEAN NOT NULL DEFAULT false;
1 change: 1 addition & 0 deletions packages/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ model User {
image String?
createdAt DateTime
updatedAt DateTime
isOnboarding Boolean @default(false)
sessions Session[]
accounts Account[]

Expand Down
Loading
Loading