Skip to content

WIP: AI assistant chat #394

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

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open

WIP: AI assistant chat #394

wants to merge 21 commits into from

Conversation

elie222
Copy link
Owner

@elie222 elie222 commented Apr 6, 2025

Summary by CodeRabbit

  • New Features

    • Launched a dedicated AI assistant chat page offering a seamless conversational experience.
    • Enhanced chat components now support message editing, markdown rendering, animated reasoning displays, tool integrations, and suggested actions.
    • Updated visual elements with new icons and refined action displays for clearer interactions.
  • Chores

    • Improved overall performance and stability through dependency updates and internal optimizations.

Copy link

vercel bot commented Apr 6, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
inbox-zero ❌ Failed (Inspect) Apr 6, 2025 11:02pm

Copy link
Contributor

coderabbitai bot commented Apr 6, 2025

Walkthrough

This pull request updates and introduces several components across the application. A test was modified to check for template variables in a nested field. The automation rules UI now replaces the old Actions component with a type-safe ActionBadges variant. New pages, API endpoints, and a suite of UI components for an AI-powered chat feature have been added, including chat interfaces, message handling, tool cards, markdown rendering, and custom hooks. In addition, dependency versions have been updated and a conditional check has been added in the SWR provider.

Changes

File(s) Change Summary
apps/web/__tests__/ai-prompt-to-rules.test.ts Updated the test to access the reply action’s content via fields.content instead of content.
apps/web/app/(app)/automation/Rules.tsx Replaced the Actions component with ActionBadges that uses the ActionType from Prisma for improved type safety.
apps/web/app/(app)/automation/chat/page.tsx
apps/web/app/api/chat/route.ts
Introduced a new assistant chat page and an API endpoint for managing chat rules with multiple Zod schema validations and a POST handler.
apps/web/components/assistant-chat/chat.tsx
apps/web/components/assistant-chat/data-stream-handler.tsx
apps/web/components/assistant-chat/icons.tsx
apps/web/components/assistant-chat/markdown.tsx
apps/web/components/assistant-chat/message-editor.tsx
apps/web/components/assistant-chat/message-reasoning.tsx
apps/web/components/assistant-chat/message.tsx
apps/web/components/assistant-chat/messages.tsx
apps/web/components/assistant-chat/multimodal-input.tsx
apps/web/components/assistant-chat/overview.tsx
apps/web/components/assistant-chat/submit-button.tsx
apps/web/components/assistant-chat/suggested-actions.tsx
apps/web/components/assistant-chat/tools.tsx
Added a series of new React components to build a comprehensive assistant chat interface, covering chat display, message editing, reasoning animations, tool management, markdown rendering, and more.
apps/web/components/assistant-chat/use-scroll-to-bottom.ts
apps/web/components/ui/textarea.tsx
Added a custom hook for auto-scrolling behavior and a new forwarded ref-based textarea component with enhanced styles.
apps/web/package.json Updated dependencies: added new libraries (@ai-sdk/react, fast-deep-equal, react-markdown, remark-gfm) and downgraded several existing packages.
apps/web/providers/SWRProvider.tsx Modified the fetcher function to include an early return of an empty array for URLs starting with /api/chat.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant MI as MultimodalInput
    participant C as Chat Component
    participant API as Chat API Endpoint
    U->>MI: Inputs message/attachment
    MI->>C: Submits message data
    C->>API: Sends POST request with message & schema data
    API->>API: Authenticates & validates input (using Zod schemas)
    API-->>C: Returns generated rules/response
    C->>U: Updates the chat interface with new messages
Loading

Possibly related PRs

  • Reply Tracker #320: Introduces a reply tracking feature interacting with the same reply action structure modified in this PR.

Poem

I hopped through lines of evolving code,
Tweaking tests and fields along the road.
New chats and badges now bloom in sight,
With streams and tools that shine so bright.
This code rabbit cheers with pure delight!

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

Scope: all 2 workspace projects
 WARN  GET http://10.0.0.28:4873/turbo error (503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.1.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/@vercel/style-guide/-/style-guide-6.0.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/eslint/-/eslint-9.23.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/typescript/-/typescript-5.8.2.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/prettier/-/prettier-3.5.3.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/eslint-config-turbo/-/eslint-config-turbo-2.4.4.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/turbo/-/turbo-2.4.4.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/@typescript-eslint/parser/-/parser-8.28.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz error (ERR_PNPM_FETCH_503). Will retry in 10 seconds. 2 retries left.
 WARN  GET http://10.0.0.28:4873/turbo error (503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/eslint/-/eslint-9.23.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.1.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/@vercel/style-guide/-/style-guide-6.0.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/typescript/-/typescript-5.8.2.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/prettier/-/prettier-3.5.3.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/eslint-config-turbo/-/eslint-config-turbo-2.4.4.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/turbo/-/turbo-2.4.4.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/@typescript-eslint/parser/-/parser-8.28.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
 WARN  GET http://10.0.0.28:4873/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz error (ERR_PNPM_FETCH_503). Will retry in 1 minute. 1 retries left.
/tmp/eslint/packages/eslint-config:
 ERR_PNPM_FETCH_503  GET http://10.0.0.28:4873/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.1.0.tgz: Service Unavailable - 503

This error happened while installing a direct dependency of /tmp/eslint/packages/eslint-config

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🧹 Nitpick comments (22)
apps/web/app/(app)/automation/chat/page.tsx (2)

4-11: Consider using dynamic chat IDs and configurable chat model

The implementation uses hardcoded values for both the chat ID and model selection. This approach might limit flexibility when multiple chat instances are needed or when different models should be used.

-export default function AssistantChatPage() {
+export default function AssistantChatPage() {
+  // Consider fetching the chat ID from URL params or generating a unique ID
+  const chatId = "1"; // or use a dynamic approach
+  const preferredModel = "gpt-4o"; // could be fetched from user preferences
+
   return (
     <>
-      <Chat id="1" initialMessages={[]} selectedChatModel="gpt-4o" />
-      <DataStreamHandler id="1" />
+      <Chat id={chatId} initialMessages={[]} selectedChatModel={preferredModel} />
+      <DataStreamHandler id={chatId} />
     </>
   );
 }

7-8: Document the relationship between Chat and DataStreamHandler components

Both components share the same ID, indicating they're designed to work together, but their relationship isn't immediately clear from the code.

Consider adding a brief comment explaining how these components interact:

 export default function AssistantChatPage() {
   return (
     <>
+      {/* Chat component handles UI rendering while DataStreamHandler processes 
+         the same chat's data stream in the background */}
       <Chat id="1" initialMessages={[]} selectedChatModel="gpt-4o" />
       <DataStreamHandler id="1" />
     </>
   );
 }
apps/web/providers/SWRProvider.tsx (1)

11-11: Document the rationale and implications of bypassing fetch for chat URLs

The new condition bypasses standard fetch behavior and error handling for chat API endpoints. While the comments above mention this is related to streaming endpoints, more explanation would help future maintainers understand the trade-offs.

   // Super hacky, if we use streaming endpoints we should do this:
   // https://github.com/vercel/ai/issues/3214
-  if (url.startsWith("/api/chat")) return [];
+  // For streaming chat endpoints, we return an empty array to avoid SWR's
+  // default fetching behavior which conflicts with the AI SDK's streaming approach.
+  // This prevents duplicate requests and allows the AI SDK to handle the streaming connection.
+  if (url.startsWith("/api/chat")) return [];
   // if (url.startsWith("/api/ai/")) return [];
apps/web/components/ui/textarea.tsx (1)

11-14: Consider breaking down long className string for improved readability

The Tailwind CSS class string is quite long, making it difficult to read and maintain.

-      className={cn(
-        "flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-base ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300 md:text-sm",
-        className,
-      )}
+      className={cn(
+        [
+          // Base styles
+          "flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white",
+          "px-3 py-2 text-base md:text-sm",
+          // Focus styles
+          "ring-offset-white focus-visible:outline-none focus-visible:ring-2",
+          "focus-visible:ring-slate-950 focus-visible:ring-offset-2",
+          // Placeholder
+          "placeholder:text-slate-500",
+          // Disabled state
+          "disabled:cursor-not-allowed disabled:opacity-50",
+          // Dark mode
+          "dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950",
+          "dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
+        ].join(" "),
+        className,
+      )}
apps/web/components/assistant-chat/overview.tsx (1)

4-24: Make the Overview component more configurable and actionable

The component currently displays a fixed message with animation. Consider making it more configurable and adding a clear call-to-action to help users get started.

-export const Overview = () => {
+export const Overview = ({ 
+  message = "Set up your AI assistant.", 
+  onSetupClick 
+}: { 
+  message?: string, 
+  onSetupClick?: () => void 
+}) => {
   return (
     <motion.div
       key="overview"
       className="mx-auto max-w-3xl md:mt-20"
       initial={{ opacity: 0, scale: 0.98 }}
       animate={{ opacity: 1, scale: 1 }}
       exit={{ opacity: 0, scale: 0.98 }}
       transition={{ delay: 0.5 }}
     >
-      <div className="flex max-w-xl flex-col gap-8 rounded-xl p-6 text-center leading-relaxed">
+      <div className="flex max-w-xl flex-col gap-8 rounded-xl p-6 text-center leading-relaxed">
         <p className="flex flex-row items-center justify-center gap-4">
           <VercelIcon size={32} />
           <span>+</span>
           <MessageIcon size={32} />
         </p>
-        <p>Set up your AI assistant.</p>
+        <p>{message}</p>
+        {onSetupClick && (
+          <button
+            onClick={onSetupClick}
+            className="mx-auto rounded-md bg-slate-900 px-4 py-2 text-sm text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200"
+          >
+            Get Started
+          </button>
+        )}
       </div>
     </motion.div>
   );
 };
apps/web/components/assistant-chat/use-scroll-to-bottom.ts (1)

1-31: Clean implementation of the scroll-to-bottom hook.

The implementation is straightforward and accomplishes the goal of automatically scrolling to the bottom of a container when its content changes. Using a MutationObserver is an efficient approach for detecting changes without constant polling.

A few suggestions for improvement:

  • Consider making the scroll behavior configurable (smooth vs instant) via an optional parameter
  • TypeScript could be strengthened by using a generic constraint like HTMLElement instead of the generic T extends HTMLElement
-export function useScrollToBottom<T extends HTMLElement>(): [
+export function useScrollToBottom<T extends HTMLElement>(options?: { behavior: ScrollBehavior }): [
  RefObject<T>,
  RefObject<T>,
] {
  const containerRef = useRef<T>(null);
  const endRef = useRef<T>(null);
+  const scrollBehavior = options?.behavior || "instant";

  useEffect(() => {
    const container = containerRef.current;
    const end = endRef.current;

    if (container && end) {
      const observer = new MutationObserver(() => {
-        end.scrollIntoView({ behavior: "instant", block: "end" });
+        end.scrollIntoView({ behavior: scrollBehavior, block: "end" });
      });

      observer.observe(container, {
        childList: true,
        subtree: true,
        attributes: true,
        characterData: true,
      });

      return () => observer.disconnect();
    }
-  }, []);
+  }, [scrollBehavior]);
apps/web/components/assistant-chat/chat.tsx (3)

11-17: Consider moving UUID generation to a utility file.

This UUID generation function is a good candidate to move to a shared utility file, especially if it might be used elsewhere in the application.

-function generateUUID(): string {
-  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
-    const r = (Math.random() * 16) | 0;
-    const v = c === "x" ? r : (r & 0x3) | 0x8;
-    return v.toString(16);
-  });
-}

Import it from a utility file instead:

import { generateUUID } from "@/lib/utils";

50-52: Improve error handling with more specific error messages.

The current error message is generic. Consider providing more context to help users understand what went wrong.

-    onError: () => {
-      toast.error("An error occured, please try again!");
+    onError: (error) => {
+      console.error("Chat error:", error);
+      toast.error("Failed to send message. Please try again.");
    },

85-98: Commented-out Artifact component.

There's a significant block of commented code. If this is work in progress, consider adding a TODO comment explaining why it's commented out and when it will be implemented.

-{/* <Artifact
-  chatId={id}
-  input={input}
-  setInput={setInput}
-  handleSubmit={handleSubmit}
-  status={status}
-  stop={stop}
-  attachments={attachments}
-  setAttachments={setAttachments}
-  append={append}
-  messages={messages}
-  setMessages={setMessages}
-  reload={reload}
-/> */}
+{/* TODO: Implement Artifact component in a future PR 
+<Artifact
+  chatId={id}
+  input={input}
+  setInput={setInput}
+  handleSubmit={handleSubmit}
+  status={status}
+  stop={stop}
+  attachments={attachments}
+  setAttachments={setAttachments}
+  append={append}
+  messages={messages}
+  setMessages={setMessages}
+  reload={reload}
+/> */}
apps/web/components/assistant-chat/icons.tsx (1)

1-168: Improve TypeScript type definitions for icon components.

All icon components should have consistent TypeScript interfaces for better type safety.

Consider defining a common interface for all icons:

interface IconProps {
  size?: number;
  className?: string;
  ariaLabel?: string;
}

export const VercelIcon = ({ size = 17, className, ariaLabel = "Vercel logo" }: IconProps) => {
  return (
    <svg
      height={size}
      width={size}
      viewBox="0 0 16 16"
      className={className}
      style={{ color: "currentcolor" }}
      role="img"
      aria-label={ariaLabel}
    >
      {/* SVG contents */}
    </svg>
  );
};

// Apply similar pattern to all other icons

This pattern allows for more flexible usage and better accessibility.

🧰 Tools
🪛 Biome (1.9.4)

[error] 3-9: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 22-28: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 97-103: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 116-122: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 135-140: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 153-159: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

apps/web/components/assistant-chat/suggested-actions.tsx (2)

54-54: Remove or implement the commented-out code.

There's a commented-out line related to URL history manipulation. If this functionality is needed, it should be implemented; otherwise, the comment should be removed.

Either implement the functionality or remove the comment:

-              // window.history.replaceState({}, "", `/chat/${chatId}`);

48-49: Simplify the key for list rendering.

The current key combines the title and index, which is unnecessarily complex. Since the array is static, the index alone would be sufficient as a key.

-          key={`suggested-action-${suggestedAction.title}-${index}`}
+          key={index}
apps/web/components/assistant-chat/message-editor.tsx (1)

14-14: Implement or remove the commented-out deleteTrailingMessages functionality.

There's commented-out code for deleting trailing messages. This suggests unfinished functionality that should either be implemented or removed.

If this functionality is needed, complete the implementation. Otherwise, remove these comments to keep the code clean.

Also applies to: 80-82

apps/web/components/assistant-chat/message-reasoning.tsx (3)

18-18: Consider initializing isExpanded to false for better UX.

The reasoning expansion state is initialized to true, which means all reasoning sections are expanded by default. For a chat interface with multiple messages, this could lead to a cluttered UI.

Consider initializing isExpanded to false and let users expand reasoning sections as needed:

-  const [isExpanded, setIsExpanded] = useState(true);
+  const [isExpanded, setIsExpanded] = useState(false);

46-46: Improve the reasoning description text.

The text "Reasoned for a few seconds" is hardcoded and may not accurately reflect the actual reasoning time or process. This could be misleading to users.

Consider using a more generic and accurate description:

-          <div className="font-medium">Reasoned for a few seconds</div>
+          <div className="font-medium">AI Reasoning</div>

Or make it dynamic based on the content or timing information if available.


55-55: Add rotation to the chevron icon for better UX.

The ChevronDownIcon should rotate when the content is expanded/collapsed to provide better visual feedback to the user.

Add a rotation transform to the icon based on the expansion state:

-            <ChevronDownIcon />
+            <ChevronDownIcon 
+              className={`transform transition-transform ${isExpanded ? 'rotate-180' : ''}`} 
+            />
apps/web/components/assistant-chat/message.tsx (1)

105-110: Enhance accessibility for loading skeletons.

When state is "call" (line 108), a <Skeleton> is displayed. Consider adding ARIA attributes or a visually hidden label indicating the content is loading so screen readers can announce it effectively.

apps/web/components/assistant-chat/multimodal-input.tsx (1)

138-145: Consider more nuanced feedback when blocking user submissions.

Currently, if the model isn't ready (line 139), all submissions are blocked with a brief error message. You might improve UX by showing a progress indicator or fallback action if the model stays stuck for too long.

apps/web/app/api/chat/route.ts (1)

353-509: Evaluate performance constraints in your AI streaming configuration.

With maxDuration = 120 and maxSteps = 10, requests could be truncated if the user provides more complex instructions. If your application requires deeper reasoning or longer responses, consider relaxing these limits or providing advanced streaming options.

apps/web/components/assistant-chat/tools.tsx (3)

93-139: Consider removing or implementing commented action details section

There's a large commented-out section that would display more detailed information about each action. Since you're using ActionBadges instead, either remove this commented code or add a TODO comment explaining why it's kept.

<div className="space-y-2">
  <h3 className="text-sm font-medium text-muted-foreground">Actions</h3>
  <ActionBadges
    actions={args.actions.map((action, i) => ({
      id: i.toString(),
      type: action.type,
    }))}
  />

-  {/* <div className="space-y-2">
-    {args.actions.map((action, i) => (
-      <div key={i} className="bg-muted p-2 rounded-md text-sm">
-        <div className="font-medium capitalize">
-          {action.type.toLowerCase().replace("_", " ")}
-        </div>
-        {action.fields &&
-          Object.entries(action.fields).filter(([_, value]) => value)
-            .length > 0 && (
-            <div className="mt-1">
-              <ul className="list-disc list-inside">
-                {action.fields.label && (
-                  <li>Label: {action.fields.label}</li>
-                )}
-                {action.fields.to && <li>To: {action.fields.to}</li>}
-                {action.fields.cc && <li>CC: {action.fields.cc}</li>}
-                {action.fields.bcc && <li>BCC: {action.fields.bcc}</li>}
-                {action.fields.subject && (
-                  <li>Subject: {action.fields.subject}</li>
-                )}
-                {action.fields.content && (
-                  <li>
-                    Content:{" "}
-                    <span className="font-mono text-xs">
-                      {action.fields.content}
-                    </span>
-                  </li>
-                )}
-                {action.fields.webhookUrl && (
-                  <li>Webhook URL: {action.fields.webhookUrl}</li>
-                )}
-              </ul>
-            </div>
-          )}
-      </div>
-    ))}
-  </div> */}
+  {/* TODO: Consider if we need detailed action information display in the future */}
</div>

144-150: Add TODOs to placeholder components

Both UpdatedRule and UpdateAbout components are placeholders with minimal implementation. Add TODO comments to clearly indicate their incomplete status.

function UpdatedRule({ args }: { args: UpdateRuleSchema }) {
-  return <Card className="p-4">UpdatedRule</Card>;
+  // TODO: Implement UpdatedRule component with proper UI
+  return <Card className="p-4">UpdatedRule</Card>;
}

function UpdateAbout({ args }: { args: UpdateAboutSchema }) {
-  return <Card className="p-4">UpdateAbout</Card>;
+  // TODO: Implement UpdateAbout component with proper UI
+  return <Card className="p-4">UpdateAbout</Card>;
}

164-171: Improve readability of the EnableReplyZero component message

The current message construction in the EnableReplyZero component reads a bit awkwardly. Consider improving the wording for better clarity.

function EnableReplyZero({ args }: { args: EnableReplyZeroSchema }) {
  return (
    <ToolWithLink href="/reply-zero">
-      Reply Zero is now {args.enabled ? "enabled" : "disabled"} and draft
-      replies are now {args.draft_replies ? "enabled" : "disabled"}
+      Reply Zero is now {args.enabled ? "enabled" : "disabled"}. 
+      Draft replies are {args.draft_replies ? "enabled" : "disabled"}.
    </ToolWithLink>
  );
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa4fd3f and 732e67d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • apps/web/__tests__/ai-prompt-to-rules.test.ts (1 hunks)
  • apps/web/app/(app)/automation/Rules.tsx (3 hunks)
  • apps/web/app/(app)/automation/chat/page.tsx (1 hunks)
  • apps/web/app/api/chat/route.ts (1 hunks)
  • apps/web/components/assistant-chat/chat.tsx (1 hunks)
  • apps/web/components/assistant-chat/data-stream-handler.tsx (1 hunks)
  • apps/web/components/assistant-chat/icons.tsx (1 hunks)
  • apps/web/components/assistant-chat/markdown.tsx (1 hunks)
  • apps/web/components/assistant-chat/message-editor.tsx (1 hunks)
  • apps/web/components/assistant-chat/message-reasoning.tsx (1 hunks)
  • apps/web/components/assistant-chat/message.tsx (1 hunks)
  • apps/web/components/assistant-chat/messages.tsx (1 hunks)
  • apps/web/components/assistant-chat/multimodal-input.tsx (1 hunks)
  • apps/web/components/assistant-chat/overview.tsx (1 hunks)
  • apps/web/components/assistant-chat/submit-button.tsx (1 hunks)
  • apps/web/components/assistant-chat/suggested-actions.tsx (1 hunks)
  • apps/web/components/assistant-chat/tools.tsx (1 hunks)
  • apps/web/components/assistant-chat/use-scroll-to-bottom.ts (1 hunks)
  • apps/web/components/ui/textarea.tsx (1 hunks)
  • apps/web/package.json (3 hunks)
  • apps/web/providers/SWRProvider.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (7)
apps/web/app/(app)/automation/chat/page.tsx (2)
apps/web/components/assistant-chat/chat.tsx (1)
  • Chat (19-101)
apps/web/components/assistant-chat/data-stream-handler.tsx (1)
  • DataStreamHandler (21-31)
apps/web/components/assistant-chat/overview.tsx (1)
apps/web/components/assistant-chat/icons.tsx (2)
  • VercelIcon (1-18)
  • MessageIcon (151-168)
apps/web/components/assistant-chat/messages.tsx (3)
apps/web/components/assistant-chat/use-scroll-to-bottom.ts (1)
  • useScrollToBottom (3-31)
apps/web/components/assistant-chat/overview.tsx (1)
  • Overview (4-24)
apps/web/components/assistant-chat/message.tsx (2)
  • PreviewMessage (130-139)
  • ThinkingMessage (141-172)
apps/web/components/assistant-chat/message-reasoning.tsx (2)
apps/web/components/assistant-chat/icons.tsx (1)
  • LoaderIcon (20-93)
apps/web/components/assistant-chat/markdown.tsx (1)
  • Markdown (103-106)
apps/web/components/assistant-chat/chat.tsx (2)
apps/web/components/assistant-chat/messages.tsx (1)
  • Messages (55-64)
apps/web/components/assistant-chat/multimodal-input.tsx (1)
  • MultimodalInput (158-167)
apps/web/components/assistant-chat/tools.tsx (2)
apps/web/app/api/chat/route.ts (5)
  • CreateRuleSchema (20-20)
  • UpdateRuleSchema (72-72)
  • UpdateAboutSchema (77-77)
  • EnableColdEmailBlockerSchema (87-89)
  • EnableReplyZeroSchema (95-95)
apps/web/app/(app)/automation/Rules.tsx (1)
  • ActionBadges (299-322)
apps/web/components/assistant-chat/multimodal-input.tsx (3)
apps/web/components/assistant-chat/suggested-actions.tsx (1)
  • SuggestedActions (74-74)
apps/web/components/ui/textarea.tsx (1)
  • Textarea (22-22)
apps/web/components/assistant-chat/icons.tsx (2)
  • StopIcon (133-149)
  • ArrowUpIcon (114-131)
🪛 Biome (1.9.4)
apps/web/components/assistant-chat/icons.tsx

[error] 3-9: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 22-28: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 97-103: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 116-122: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 135-140: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 153-159: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🔇 Additional comments (23)
apps/web/app/(app)/automation/chat/page.tsx (1)

1-2: LGTM: Clean import statements

The imports are concise and properly reference the required components.

apps/web/components/ui/textarea.tsx (1)

5-19: Well-implemented reusable Textarea component

The component correctly implements the React.forwardRef pattern and provides comprehensive styling with support for various states (focus, disabled) and dark mode.

apps/web/components/assistant-chat/overview.tsx (1)

6-13: LGTM: Good use of framer-motion for smooth animations

The animation configuration provides a clean fade-in/scale effect with appropriate delay.

apps/web/__tests__/ai-prompt-to-rules.test.ts (1)

332-332: Updated field path for content verification

The test now correctly accesses template variables via the nested fields.content property instead of directly from content, which aligns with the updated data structure returned by aiPromptToRules. This change ensures template variables are properly verified in the nested structure.

apps/web/components/assistant-chat/markdown.tsx (2)

1-5: LGTM! Proper imports for markdown rendering

The imports are appropriate for creating a markdown renderer component with React, including the necessary packages for markdown processing.


93-106: Good implementation of memoization for performance optimization

The use of memo with a custom comparison function is a good optimization to prevent unnecessary re-renders. This is particularly important for markdown rendering, which can be expensive for large content.

apps/web/app/(app)/automation/Rules.tsx (3)

53-53: LGTM! Import ActionType for type safety

Adding the ActionType import from @prisma/client provides proper type safety for the actions.


146-146: Updated component usage with type-safe version

Replaced the old Actions component with the new type-safe ActionBadges component.


299-322: Well-structured type-safe ActionBadges component

The implementation of ActionBadges provides better type safety with explicit typing of action objects. The component is now exported, making it reusable across the application.

The component efficiently renders badge elements with appropriate styling based on action types, improving maintainability compared to the previous implementation.

apps/web/components/assistant-chat/data-stream-handler.tsx (2)

1-19: Well-defined type for handling different data stream events

The DataStreamDelta type is comprehensive and covers various types of content that might be streamed in a chat interface. The client directive is appropriately placed at the top of the file.


21-31: Component tracks stream data but doesn't expose functionality

The component tracks the last processed index in the data stream but doesn't expose any way to use this information. It appears to be missing either:

  1. A way to notify parent components about processed data
  2. Some effect that uses the processed data

Consider adding a callback prop or context integration if this component is meant to process data for other components.

Is this component intended to work with a context provider or event system that's not visible in this file? This might explain why it's tracking data but not doing anything visible with it.

apps/web/components/assistant-chat/submit-button.tsx (1)

1-36: Well-designed accessible submit button with loading state.

The button correctly handles loading states and includes proper accessibility attributes. Using useFormStatus is a modern approach that works well with React's form handling.

apps/web/package.json (2)

29-29:

❓ Verification inconclusive

Verify compatibility with the downgraded @hookform/resolvers package.

You've downgraded @hookform/resolvers from 4.1.3 to 3.9.1, which is a major version downgrade. This could introduce breaking changes or compatibility issues with other packages like react-hook-form.


🏁 Script executed:

#!/bin/bash
# Check if react-hook-form version is compatible with @hookform/resolvers 3.9.1
grep -A 5 "\"peerDependencies\"" node_modules/@hookform/resolvers/package.json

# Check for imports of @hookform/resolvers in the codebase
rg -l "@hookform/resolvers" --type tsx --type ts

Length of output: 234


Action Required: Verify Compatibility of Downgraded Package

The initial automated checks did not yield the expected output:

  • The peer dependency information from @hookform/resolvers couldn’t be retrieved (node_modules not found or package not installed).
  • The file search encountered an issue recognizing the "tsx" file type.

Because these results are inconclusive, please manually verify that downgrading @hookform/resolvers from 4.1.3 to 3.9.1 does not introduce any breaking changes with react-hook-form. Ensure that:

  • The version of react-hook-form in use is compatible with @hookform/resolvers 3.9.1.
  • There are no runtime or integration issues in parts of the code where @hookform/resolvers is imported/used.

138-139:

❓ Verification inconclusive

Document the reason for dependency downgrades.

Several dependencies have been downgraded:

  • styled-components from 6.1.16 to 6.1.13
  • swr from 2.3.3 to 2.3.0

Downgrading packages can introduce compatibility issues or miss out on bug fixes and security patches. Please document the rationale behind these changes.


🌐 Web query:

What are the differences between swr 2.3.3 and 2.3.0?

💡 Result:

The differences between SWR versions 2.3.3 and 2.3.0 aren't explicitly detailed in the provided search results. However, based on standard semantic versioning practices and SWR's development patterns:

  1. Patch-level changes:
    Version 2.3.3 likely contains bug fixes and minor improvements over 2.3.0, as the third digit in semver indicates patch updates. These might include:

    • Memory leak fixes
    • Edge case handling for revalidation
    • TypeScript definition improvements
  2. No breaking changes:
    Since both versions share the same major (2) and minor (3) version numbers, there are no API changes or feature removals between them[2][3].

For specific details, you'd need to consult SWR's official GitHub release notes, as the provided sources don't include granular version comparisons between these exact releases. The npm registry shows SWR follows aggressive versioning with frequent patches, often addressing performance optimizations and React 18 compatibility issues[2][10].

Citations:


Action Required: Document Dependency Downgrade Rationale

The dependency updates in apps/web/package.json (lines 138‑139) include downgrades for both styled-components (from 6.1.16 to 6.1.13) and swr (from 2.3.3 to 2.3.0). For clarity and future maintenance, please document the following:

  • swr:
    Although versions 2.3.3 and 2.3.0 share the same major and minor numbers, the 2.3.3 patch likely included bug fixes (e.g., addressing memory leaks, improving edge-case handling, and updating TypeScript definitions). If the decision to downgrade to 2.3.0 was made to avoid any unexpected issues introduced in the patch fixes, please explicitly state that rationale.

  • styled-components:
    Similarly, if the downgrade to 6.1.13 was aimed at resolving compatibility problems or regressing issues introduced in 6.1.16, please provide details on this decision.

Documenting these reasons (either in the commit message or an associated changelog) will help prevent confusion and aid in maintaining compatibility across the application.

apps/web/components/assistant-chat/message-editor.tsx (1)

95-95:

❓ Verification inconclusive

Verify the intention of removing trailing messages.

When updating a message, the code only keeps messages up to the edited message:

return [...messages.slice(0, index), updatedMessage];

This will remove any messages that came after the edited message. Confirm if this is the intended behavior.


🏁 Script executed:

#!/bin/bash
# Check if there are any places in the codebase that rely on message ordering
rg -A 3 "setMessages" --type tsx --type ts | grep -B 5 -A 5 "slice"

Length of output: 93


Action Required: Verify Truncation Intent of Message Updates

The code currently updates a message by returning only the portion of the messages array up to the edited message:

return [...messages.slice(0, index), updatedMessage];

This effectively discards any messages that come after the edited message. The automated search using rg encountered an issue recognizing TSX files, so we couldn't confirm whether any part of the codebase relies on the trailing messages. Please manually verify that discarding these trailing messages aligns with the intended behavior—particularly ensuring that no downstream logic or component depends on the full message history.

apps/web/components/assistant-chat/messages.tsx (1)

17-22: Great structure for the component logic.

The PureMessages function neatly deconstructs the props and sets up the scroll references, making the implementation straightforward and easy to follow.

apps/web/components/assistant-chat/message.tsx (2)

70-85: Verify Markdown sanitization for user-generated content.

When rendering arbitrary text as Markdown (line 81), ensure the Markdown component or its underlying library is properly sanitizing user input to prevent potential XSS attacks.


130-139: Deep-equality memoization looks good.

The custom comparison function for PreviewMessage is straightforward and ensures re-renders only happen when message content or loading states actually change.

apps/web/components/assistant-chat/multimodal-input.tsx (2)

72-91: Revisit storing input in localStorage for privacy.

User messages are retained in localStorage between sessions. Consider user privacy expectations, especially on shared devices, and provide an opt-out or a more secure storage approach if needed.


169-184: Double-check the no-op setMessages call in StopButton.

Line 183 calls setMessages((messages) => messages), which doesn't appear to change state. Confirm if you intended to update the messages array or if this is just a placeholder, since it can be removed if unnecessary.

apps/web/components/assistant-chat/tools.tsx (3)

1-14: LGTM! Well-organized imports

The imports are well-organized, cleanly separating external dependencies, internal components, and types.


152-162: LGTM! Clean implementation of EnableColdEmailBlocker

The EnableColdEmailBlocker component is well-implemented, reusing the ToolWithLink component for consistent UI.


173-190: LGTM! Well-designed reusable ToolWithLink component

The ToolWithLink component is a good example of component reuse, providing consistent UI for tool cards with links.

Comment on lines +6 to +91
const components: Partial<Components> = {
pre: ({ children }) => <>{children}</>,
ol: ({ node, children, ...props }) => {
return (
<ol className="ml-4 list-outside list-decimal" {...props}>
{children}
</ol>
);
},
li: ({ node, children, ...props }) => {
return (
<li className="py-1" {...props}>
{children}
</li>
);
},
ul: ({ node, children, ...props }) => {
return (
<ul className="ml-4 list-outside list-decimal" {...props}>
{children}
</ul>
);
},
strong: ({ node, children, ...props }) => {
return (
<span className="font-semibold" {...props}>
{children}
</span>
);
},
a: ({ node, children, ...props }) => {
return (
// @ts-expect-error
<Link
className="text-blue-500 hover:underline"
target="_blank"
rel="noreferrer"
{...props}
>
{children}
</Link>
);
},
h1: ({ node, children, ...props }) => {
return (
<h1 className="mb-2 mt-6 text-3xl font-semibold" {...props}>
{children}
</h1>
);
},
h2: ({ node, children, ...props }) => {
return (
<h2 className="mb-2 mt-6 text-2xl font-semibold" {...props}>
{children}
</h2>
);
},
h3: ({ node, children, ...props }) => {
return (
<h3 className="mb-2 mt-6 text-xl font-semibold" {...props}>
{children}
</h3>
);
},
h4: ({ node, children, ...props }) => {
return (
<h4 className="mb-2 mt-6 text-lg font-semibold" {...props}>
{children}
</h4>
);
},
h5: ({ node, children, ...props }) => {
return (
<h5 className="mb-2 mt-6 text-base font-semibold" {...props}>
{children}
</h5>
);
},
h6: ({ node, children, ...props }) => {
return (
<h6 className="mb-2 mt-6 text-sm font-semibold" {...props}>
{children}
</h6>
);
},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving list styling and addressing the type error in Link component

The component setup for rendering markdown elements looks good, but there are a few issues:

  1. Both ordered (ol) and unordered (ul) lists are using the same styling class list-decimal, which will cause unordered lists to display with numbers instead of bullets.
  2. There's a @ts-expect-error comment for the Link component that should be resolved.

For the list styling issue, apply this change:

  ul: ({ node, children, ...props }) => {
    return (
-      <ul className="ml-4 list-outside list-decimal" {...props}>
+      <ul className="ml-4 list-outside list-disc" {...props}>
        {children}
      </ul>
    );
  },

For the Link type error, consider properly typing the props or using a more specific type assertion instead of suppressing the error.

📝 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 components: Partial<Components> = {
pre: ({ children }) => <>{children}</>,
ol: ({ node, children, ...props }) => {
return (
<ol className="ml-4 list-outside list-decimal" {...props}>
{children}
</ol>
);
},
li: ({ node, children, ...props }) => {
return (
<li className="py-1" {...props}>
{children}
</li>
);
},
ul: ({ node, children, ...props }) => {
return (
<ul className="ml-4 list-outside list-decimal" {...props}>
{children}
</ul>
);
},
strong: ({ node, children, ...props }) => {
return (
<span className="font-semibold" {...props}>
{children}
</span>
);
},
a: ({ node, children, ...props }) => {
return (
// @ts-expect-error
<Link
className="text-blue-500 hover:underline"
target="_blank"
rel="noreferrer"
{...props}
>
{children}
</Link>
);
},
h1: ({ node, children, ...props }) => {
return (
<h1 className="mb-2 mt-6 text-3xl font-semibold" {...props}>
{children}
</h1>
);
},
h2: ({ node, children, ...props }) => {
return (
<h2 className="mb-2 mt-6 text-2xl font-semibold" {...props}>
{children}
</h2>
);
},
h3: ({ node, children, ...props }) => {
return (
<h3 className="mb-2 mt-6 text-xl font-semibold" {...props}>
{children}
</h3>
);
},
h4: ({ node, children, ...props }) => {
return (
<h4 className="mb-2 mt-6 text-lg font-semibold" {...props}>
{children}
</h4>
);
},
h5: ({ node, children, ...props }) => {
return (
<h5 className="mb-2 mt-6 text-base font-semibold" {...props}>
{children}
</h5>
);
},
h6: ({ node, children, ...props }) => {
return (
<h6 className="mb-2 mt-6 text-sm font-semibold" {...props}>
{children}
</h6>
);
},
};
const components: Partial<Components> = {
pre: ({ children }) => <>{children}</>,
ol: ({ node, children, ...props }) => {
return (
<ol className="ml-4 list-outside list-decimal" {...props}>
{children}
</ol>
);
},
li: ({ node, children, ...props }) => {
return (
<li className="py-1" {...props}>
{children}
</li>
);
},
ul: ({ node, children, ...props }) => {
return (
<ul className="ml-4 list-outside list-disc" {...props}>
{children}
</ul>
);
},
strong: ({ node, children, ...props }) => {
return (
<span className="font-semibold" {...props}>
{children}
</span>
);
},
a: ({ node, children, ...props }) => {
return (
// @ts-expect-error
<Link
className="text-blue-500 hover:underline"
target="_blank"
rel="noreferrer"
{...props}
>
{children}
</Link>
);
},
h1: ({ node, children, ...props }) => {
return (
<h1 className="mb-2 mt-6 text-3xl font-semibold" {...props}>
{children}
</h1>
);
},
h2: ({ node, children, ...props }) => {
return (
<h2 className="mb-2 mt-6 text-2xl font-semibold" {...props}>
{children}
</h2>
);
},
h3: ({ node, children, ...props }) => {
return (
<h3 className="mb-2 mt-6 text-xl font-semibold" {...props}>
{children}
</h3>
);
},
h4: ({ node, children, ...props }) => {
return (
<h4 className="mb-2 mt-6 text-lg font-semibold" {...props}>
{children}
</h4>
);
},
h5: ({ node, children, ...props }) => {
return (
<h5 className="mb-2 mt-6 text-base font-semibold" {...props}>
{children}
</h5>
);
},
h6: ({ node, children, ...props }) => {
return (
<h6 className="mb-2 mt-6 text-sm font-semibold" {...props}>
{children}
</h6>
);
},
};

Comment on lines +133 to +149
export const StopIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3 3H13V13H3V3Z"
fill="currentColor"
/>
</svg>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add accessibility attributes to StopIcon.

Add appropriate ARIA attributes to this SVG icon.

export const StopIcon = ({ size = 16 }: { size?: number }) => {
  return (
    <svg
      height={size}
      viewBox="0 0 16 16"
      width={size}
      style={{ color: "currentcolor" }}
+     role="img"
+     aria-label="Stop"
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M3 3H13V13H3V3Z"
        fill="currentColor"
      />
    </svg>
  );
};
📝 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 const StopIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3 3H13V13H3V3Z"
fill="currentColor"
/>
</svg>
);
};
export const StopIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
role="img"
aria-label="Stop"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3 3H13V13H3V3Z"
fill="currentColor"
/>
</svg>
);
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 135-140: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

Comment on lines +1 to +18
export const VercelIcon = ({ size = 17 }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 1L16 15H0L8 1Z"
fill="currentColor"
/>
</svg>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add accessibility attributes to SVG icons.

SVGs without accessibility attributes can create issues for screen readers. Add appropriate ARIA attributes to make them accessible.

export const VercelIcon = ({ size = 17 }) => {
  return (
    <svg
      height={size}
      strokeLinejoin="round"
      viewBox="0 0 16 16"
      width={size}
      style={{ color: "currentcolor" }}
+     role="img"
+     aria-label="Vercel logo"
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M8 1L16 15H0L8 1Z"
        fill="currentColor"
      />
    </svg>
  );
};
📝 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 const VercelIcon = ({ size = 17 }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 1L16 15H0L8 1Z"
fill="currentColor"
/>
</svg>
);
};
export const VercelIcon = ({ size = 17 }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
role="img"
aria-label="Vercel logo"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 1L16 15H0L8 1Z"
fill="currentColor"
/>
</svg>
);
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 3-9: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

Comment on lines +20 to +93
export const LoaderIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<g clipPath="url(#clip0_2393_1490)">
<path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M8 16V12"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.9"
d="M3.29773 1.52783L5.64887 4.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.1"
d="M12.7023 1.52783L10.3511 4.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.4"
d="M12.7023 14.472L10.3511 11.236"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.6"
d="M3.29773 14.472L5.64887 11.236"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.2"
d="M15.6085 5.52783L11.8043 6.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.7"
d="M0.391602 10.472L4.19583 9.23598"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.3"
d="M15.6085 10.4722L11.8043 9.2361"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.8"
d="M0.391602 5.52783L4.19583 6.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
</g>
<defs>
<clipPath id="clip0_2393_1490">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add accessibility attributes to LoaderIcon.

Add appropriate ARIA attributes to this SVG icon.

export const LoaderIcon = ({ size = 16 }: { size?: number }) => {
  return (
    <svg
      height={size}
      strokeLinejoin="round"
      viewBox="0 0 16 16"
      width={size}
      style={{ color: "currentcolor" }}
+     role="img"
+     aria-label="Loading indicator"
    >
      <g clipPath="url(#clip0_2393_1490)">
        <path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
        <path
          opacity="0.5"
          d="M8 16V12"
          stroke="currentColor"
          strokeWidth="1.5"
        />
        <!-- other paths -->
      </g>
      <defs>
        <clipPath id="clip0_2393_1490">
          <rect width="16" height="16" fill="white" />
        </clipPath>
      </defs>
    </svg>
  );
};
📝 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 const LoaderIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<g clipPath="url(#clip0_2393_1490)">
<path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M8 16V12"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.9"
d="M3.29773 1.52783L5.64887 4.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.1"
d="M12.7023 1.52783L10.3511 4.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.4"
d="M12.7023 14.472L10.3511 11.236"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.6"
d="M3.29773 14.472L5.64887 11.236"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.2"
d="M15.6085 5.52783L11.8043 6.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.7"
d="M0.391602 10.472L4.19583 9.23598"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.3"
d="M15.6085 10.4722L11.8043 9.2361"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.8"
d="M0.391602 5.52783L4.19583 6.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
</g>
<defs>
<clipPath id="clip0_2393_1490">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
};
export const LoaderIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
role="img"
aria-label="Loading indicator"
>
<g clipPath="url(#clip0_2393_1490)">
<path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M8 16V12"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.9"
d="M3.29773 1.52783L5.64887 4.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.1"
d="M12.7023 1.52783L10.3511 4.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.4"
d="M12.7023 14.472L10.3511 11.236"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.6"
d="M3.29773 14.472L5.64887 11.236"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.2"
d="M15.6085 5.52783L11.8043 6.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.7"
d="M0.391602 10.472L4.19583 9.23598"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.3"
d="M15.6085 10.4722L11.8043 9.2361"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.8"
d="M0.391602 5.52783L4.19583 6.7639"
stroke="currentColor"
strokeWidth="1.5"
/>
</g>
<defs>
<clipPath id="clip0_2393_1490">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 22-28: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

Comment on lines +114 to +131
export const ArrowUpIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z"
fill="currentColor"
/>
</svg>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add accessibility attributes to ArrowUpIcon.

Add appropriate ARIA attributes to this SVG icon.

export const ArrowUpIcon = ({ size = 16 }: { size?: number }) => {
  return (
    <svg
      height={size}
      strokeLinejoin="round"
      viewBox="0 0 16 16"
      width={size}
      style={{ color: "currentcolor" }}
+     role="img"
+     aria-label="Arrow up"
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z"
        fill="currentColor"
      />
    </svg>
  );
};
📝 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 const ArrowUpIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z"
fill="currentColor"
/>
</svg>
);
};
export const ArrowUpIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: "currentcolor" }}
role="img"
aria-label="Arrow up"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z"
fill="currentColor"
/>
</svg>
);
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 116-122: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

Comment on lines +422 to +431
const rule = await prisma.rule.findUnique({
where: { id: ruleName, userId },
});

if (!rule)
return {
error:
"Rule not found. Try listing the rules again. The user may have made changes since you last checked.",
};

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Confirm consistency between “ruleName” and the database “id”.

The code uses { id: ruleName, userId } as the lookup keys. If “ruleName” is not the actual primary key in your schema, this could cause mismatches or failed lookups. Verify the naming matches your Prisma model.

Comment on lines +359 to +416
create_rule: tool({
description: "Create a new rule",
// parameters: categories
// ? getCreateRuleSchemaWithCategories(
// categories.map((c) => c.name) as [string, ...string[]],
// )
// : createRuleSchema,
parameters: createRuleSchema,
execute: async ({ name, condition, actions }) => {
logger.info("Create Rule", { name, condition, actions });
// trackToolCall("create_rule", user.email);

// const conditions =
// condition as CreateRuleSchemaWithCategories["condition"];

try {
// const categoryIds = await getUserCategoriesForNames(
// userId,
// conditions.categories?.categoryFilters || [],
// );

const rule = await createRule({
result: { name, condition, actions },
userId,
categoryIds: [],
});

if ("error" in rule) {
logger.error("Error while creating rule", {
// ...loggerOptions,
error: rule.error,
});

return {
error: "Failed to create rule",
message: rule.error,
};
}

// createdRules.set(rule.id, rule);

return { success: true };
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);

logger.error("Failed to create rule", {
// ...loggerOptions,
error: message,
});

return {
error: "Failed to create rule",
message,
};
}
},
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle potential concurrency or duplicate creation scenarios.

When creating rules (line 380), multiple concurrent requests could insert duplicates or partially updated data. Consider placing this logic in a transaction and/or enforcing unique constraints to safeguard integrity.

Comment on lines +15 to +30
export function ToolCard({ toolName, args }: { toolName: string; args: any }) {
switch (toolName) {
case "create_rule":
return <CreatedRule args={args as CreateRuleSchema} />;
case "update_rule":
return <UpdatedRule args={args as UpdateRuleSchema} />;
case "update_about":
return <UpdateAbout args={args as UpdateAboutSchema} />;
case "enable_cold_email_blocker":
return (
<EnableColdEmailBlocker args={args as EnableColdEmailBlockerSchema} />
);
case "enable_reply_zero":
return <EnableReplyZero args={args as EnableReplyZeroSchema} />;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety in ToolCard component

The ToolCard component uses any for the args prop which loses type safety. Consider using a union type for better type checking. Additionally, there's no default case in the switch statement to handle unrecognized tool names.

-export function ToolCard({ toolName, args }: { toolName: string; args: any }) {
+export function ToolCard({ 
+  toolName, 
+  args 
+}: { 
+  toolName: string; 
+  args: CreateRuleSchema | UpdateRuleSchema | UpdateAboutSchema | EnableColdEmailBlockerSchema | EnableReplyZeroSchema 
+}) {
  switch (toolName) {
    case "create_rule":
      return <CreatedRule args={args as CreateRuleSchema} />;
    case "update_rule":
      return <UpdatedRule args={args as UpdateRuleSchema} />;
    case "update_about":
      return <UpdateAbout args={args as UpdateAboutSchema} />;
    case "enable_cold_email_blocker":
      return (
        <EnableColdEmailBlocker args={args as EnableColdEmailBlockerSchema} />
      );
    case "enable_reply_zero":
      return <EnableReplyZero args={args as EnableReplyZeroSchema} />;
+    default:
+      console.warn(`Unknown tool name: ${toolName}`);
+      return null;
  }
}
📝 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 ToolCard({ toolName, args }: { toolName: string; args: any }) {
switch (toolName) {
case "create_rule":
return <CreatedRule args={args as CreateRuleSchema} />;
case "update_rule":
return <UpdatedRule args={args as UpdateRuleSchema} />;
case "update_about":
return <UpdateAbout args={args as UpdateAboutSchema} />;
case "enable_cold_email_blocker":
return (
<EnableColdEmailBlocker args={args as EnableColdEmailBlockerSchema} />
);
case "enable_reply_zero":
return <EnableReplyZero args={args as EnableReplyZeroSchema} />;
}
}
export function ToolCard({
toolName,
args
}: {
toolName: string;
args: CreateRuleSchema | UpdateRuleSchema | UpdateAboutSchema | EnableColdEmailBlockerSchema | EnableReplyZeroSchema
}) {
switch (toolName) {
case "create_rule":
return <CreatedRule args={args as CreateRuleSchema} />;
case "update_rule":
return <UpdatedRule args={args as UpdateRuleSchema} />;
case "update_about":
return <UpdateAbout args={args as UpdateAboutSchema} />;
case "enable_cold_email_blocker":
return (
<EnableColdEmailBlocker args={args as EnableColdEmailBlockerSchema} />
);
case "enable_reply_zero":
return <EnableReplyZero args={args as EnableReplyZeroSchema} />;
default:
console.warn(`Unknown tool name: ${toolName}`);
return null;
}
}

Comment on lines +32 to +52
function CreatedRule({ args }: { args: CreateRuleSchema }) {
const conditionsArray = [
args.condition.aiInstructions,
args.condition.static,
].filter(Boolean);

return (
<Card className="space-y-3 p-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Added Rule: {args.name}</h3>

<Button
variant="ghost"
size="icon"
onClick={() => {
const yes = confirm("Are you sure you want to delete this rule?");
if (yes) {
// deleteRule(args.id);
toastSuccess({ description: "The rule has been deleted." });
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement the commented delete functionality

The delete button has UI and confirmation logic, but the actual deletion function is commented out. This creates a confusing user experience where clicking "Yes" on the confirmation dialog only shows a success toast without actually performing the deletion.

Either implement the delete functionality or disable the button until the functionality is ready:

<Button
  variant="ghost"
  size="icon"
+  disabled={true} // Remove when delete functionality is implemented
  onClick={() => {
    const yes = confirm("Are you sure you want to delete this rule?");
    if (yes) {
-     // deleteRule(args.id);
+     // TODO: Implement deleteRule functionality
      toastSuccess({ description: "The rule has been deleted." });
    }
  }}
>
📝 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
function CreatedRule({ args }: { args: CreateRuleSchema }) {
const conditionsArray = [
args.condition.aiInstructions,
args.condition.static,
].filter(Boolean);
return (
<Card className="space-y-3 p-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Added Rule: {args.name}</h3>
<Button
variant="ghost"
size="icon"
onClick={() => {
const yes = confirm("Are you sure you want to delete this rule?");
if (yes) {
// deleteRule(args.id);
toastSuccess({ description: "The rule has been deleted." });
}
}}
function CreatedRule({ args }: { args: CreateRuleSchema }) {
const conditionsArray = [
args.condition.aiInstructions,
args.condition.static,
].filter(Boolean);
return (
<Card className="space-y-3 p-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Added Rule: {args.name}</h3>
<Button
variant="ghost"
size="icon"
disabled={true} // Remove when delete functionality is implemented
onClick={() => {
const yes = confirm("Are you sure you want to delete this rule?");
if (yes) {
// TODO: Implement deleteRule functionality
toastSuccess({ description: "The rule has been deleted." });
}
}}
>

Comment on lines +58 to +101
<div className="space-y-2">
{/* <h3 className="text-sm font-medium text-muted-foreground">
Conditions
</h3> */}
<div className="rounded-md bg-muted p-2 text-sm">
{args.condition.aiInstructions && (
<div className="flex">
<SparklesIcon className="mr-2 size-6" />
{args.condition.aiInstructions}
</div>
)}
{conditionsArray.length > 1 && (
<div className="my-2 font-mono text-xs">
{args.condition.conditionalOperator || "AND"}
</div>
)}
{args.condition.static && (
<div className="mt-1">
<span className="font-medium">Static Conditions:</span>
<ul className="mt-1 list-inside list-disc">
{args.condition.static.from && (
<li>From: {args.condition.static.from}</li>
)}
{args.condition.static.to && (
<li>To: {args.condition.static.to}</li>
)}
{args.condition.static.subject && (
<li>Subject: {args.condition.static.subject}</li>
)}
</ul>
</div>
)}
</div>
</div>

<div className="space-y-2">
<h3 className="text-sm font-medium text-muted-foreground">Actions</h3>
<ActionBadges
actions={args.actions.map((action, i) => ({
id: i.toString(),
type: action.type,
}))}
/>

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clean up commented code and improve rendering logic

There are commented sections of code in the component that should be either implemented or removed. Additionally, the component doesn't handle potential undefined values in the nested args.condition object.

Remove commented code that's no longer needed, and add null checks:

<div className="space-y-2">
-  {/* <h3 className="text-sm font-medium text-muted-foreground">
-    Conditions
-  </h3> */}
  <div className="rounded-md bg-muted p-2 text-sm">
-    {args.condition.aiInstructions && (
+    {args.condition?.aiInstructions && (
      <div className="flex">
        <SparklesIcon className="mr-2 size-6" />
        {args.condition.aiInstructions}
      </div>
    )}
    {conditionsArray.length > 1 && (
      <div className="my-2 font-mono text-xs">
-        {args.condition.conditionalOperator || "AND"}
+        {args.condition?.conditionalOperator || "AND"}
      </div>
    )}
-    {args.condition.static && (
+    {args.condition?.static && (
📝 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
<div className="space-y-2">
{/* <h3 className="text-sm font-medium text-muted-foreground">
Conditions
</h3> */}
<div className="rounded-md bg-muted p-2 text-sm">
{args.condition.aiInstructions && (
<div className="flex">
<SparklesIcon className="mr-2 size-6" />
{args.condition.aiInstructions}
</div>
)}
{conditionsArray.length > 1 && (
<div className="my-2 font-mono text-xs">
{args.condition.conditionalOperator || "AND"}
</div>
)}
{args.condition.static && (
<div className="mt-1">
<span className="font-medium">Static Conditions:</span>
<ul className="mt-1 list-inside list-disc">
{args.condition.static.from && (
<li>From: {args.condition.static.from}</li>
)}
{args.condition.static.to && (
<li>To: {args.condition.static.to}</li>
)}
{args.condition.static.subject && (
<li>Subject: {args.condition.static.subject}</li>
)}
</ul>
</div>
)}
</div>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium text-muted-foreground">Actions</h3>
<ActionBadges
actions={args.actions.map((action, i) => ({
id: i.toString(),
type: action.type,
}))}
/>
<div className="space-y-2">
<div className="rounded-md bg-muted p-2 text-sm">
{args.condition?.aiInstructions && (
<div className="flex">
<SparklesIcon className="mr-2 size-6" />
{args.condition.aiInstructions}
</div>
)}
{conditionsArray.length > 1 && (
<div className="my-2 font-mono text-xs">
{args.condition?.conditionalOperator || "AND"}
</div>
)}
{args.condition?.static && (
<div className="mt-1">
<span className="font-medium">Static Conditions:</span>
<ul className="mt-1 list-inside list-disc">
{args.condition.static.from && (
<li>From: {args.condition.static.from}</li>
)}
{args.condition.static.to && (
<li>To: {args.condition.static.to}</li>
)}
{args.condition.static.subject && (
<li>Subject: {args.condition.static.subject}</li>
)}
</ul>
</div>
)}
</div>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium text-muted-foreground">Actions</h3>
<ActionBadges
actions={args.actions.map((action, i) => ({
id: i.toString(),
type: action.type,
}))}
/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant