Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
"use client";

import { useParams, useRouter } from "next/navigation";
import { createContext, useContext } from "react";
import { createContext, type PropsWithChildren, useContext } from "react";

type AgentChatContextValue = {
interface AgentChatContextValue {
chatId: string;
setChatId: (id: string) => void;
};
}

const AgentChatContext = createContext<AgentChatContextValue | null>(null);

type AgentChatProviderProps = {
interface AgentChatProviderProps extends PropsWithChildren {
chatId: string;
children: React.ReactNode;
};
}

export function AgentChatProvider({
chatId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { PaperPlaneRightIcon, StopIcon } from "@phosphor-icons/react";
import { useAtom } from "jotai";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { useChat } from "@/contexts/chat-context";
import { useEnterSubmit } from "@/hooks/use-enter-submit";
import { cn } from "@/lib/utils";
Expand Down Expand Up @@ -37,7 +37,7 @@ export function AgentInput() {
setInput("");
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
agentCommands.handleInputChange(
e.target.value,
e.target.selectionStart ?? 0
Expand All @@ -57,12 +57,14 @@ export function AgentInput() {

<form className="flex gap-2" onSubmit={handleSubmit} ref={formRef}>
<div className="relative flex-1">
<Input
<Textarea
className={cn(
"h-12 pr-24 pl-4 text-base",
"px-4 text-base",
"focus:ring-2 focus:ring-primary/20"
)}
disabled={isLoading}
maxRows={4}
minRows={1}
onChange={handleChange}
onKeyDown={onKeyDown}
placeholder="Ask the agent to analyze your data..."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import { AgentInput } from "./agent-input";
import { AgentMessages } from "./agent-messages";
import { NewChatButton } from "./new-chat-button";

type AgentPageContentProps = {
interface AgentPageContentProps {
chatId: string;
websiteId: string;
};
}

const SUGGESTED_PROMPTS = [
{
Expand All @@ -51,13 +51,10 @@ const SUGGESTED_PROMPTS = [
},
];

export function AgentPageContent({
chatId,
websiteId: _websiteId,
}: AgentPageContentProps) {
export function AgentPageContent({ chatId, websiteId }: AgentPageContentProps) {
return (
<AgentChatProvider chatId={chatId}>
<AgentPageContentInner websiteId={_websiteId} />
<AgentPageContentInner websiteId={websiteId} />
</AgentChatProvider>
);
}
Expand All @@ -78,7 +75,6 @@ function AgentPageContentInner({
className={cn(
"flex flex-1 flex-col overflow-hidden",
"transition-all duration-300 ease-in-out",
false
)}
>
<div className="relative z-10 bg-sidebar-accent">
Expand Down Expand Up @@ -111,7 +107,7 @@ function AgentPageContentInner({
</div>

<Conversation className="flex-1">
<ConversationContent className="mx-auto w-full max-w-4xl pb-[150px]">
<ConversationContent className="mx-auto w-full max-w-4xl">
{hasMessages ? (
<AgentMessages />
) : (
Expand All @@ -135,23 +131,25 @@ function WelcomeState({
onPromptSelect: (text: string) => void;
}) {
return (
<div className="flex min-h-[400px] flex-col items-center justify-center py-8">
<Avatar className="size-10">
<AvatarImage alt="Databunny avatar" src="/databunny.webp" />
<AvatarFallback className="bg-primary/10 font-semibold text-primary">
DB
</AvatarFallback>
</Avatar>
<div className="min-h-[400px] space-y-6 py-8">
<div className="flex flex-col items-center justify-center">
<Avatar className="size-10">
<AvatarImage alt="Databunny avatar" src="/databunny.webp" />
<AvatarFallback className="bg-primary/10 font-semibold text-primary">
DB
</AvatarFallback>
</Avatar>

<div className="mb-8 max-w-md text-center">
<h3 className="mb-2 font-semibold text-xl">Meet Databunny</h3>
<p className="text-balance text-foreground/60 text-sm leading-relaxed">
Databunny explores your analytics, uncovers patterns, and surfaces
actionable insights without you babysitting every step.
</p>
<div className="max-w-md space-y-2 text-center">
<h3 className="font-semibold text-xl">Meet Databunny</h3>
<p className="text-balance text-foreground/60 text-sm leading-relaxed">
Databunny explores your analytics, uncovers patterns, and surfaces
actionable insights without you babysitting every step.
</p>
</div>
</div>

<div className="mb-8 flex flex-wrap justify-center gap-2">
<div className="flex flex-wrap justify-center gap-2">
{[
"Deep Analysis",
"Pattern Detection",
Expand All @@ -167,37 +165,31 @@ function WelcomeState({
))}
</div>

<div className="w-full max-w-lg space-y-3">
<div className="flex items-center gap-2 text-foreground/50 text-sm">
<LightningIcon className="size-4" weight="duotone" />
<span>Try asking:</span>
</div>
<div className="grid gap-2 sm:grid-cols-2">
{SUGGESTED_PROMPTS.map((prompt) => (
<button
className={cn(
"group flex items-start gap-3 rounded border border-dashed p-3 text-left",
"transition-all hover:border-solid hover:bg-accent/30",
"disabled:cursor-not-allowed disabled:opacity-50"
)}
key={prompt.text}
onClick={() => onPromptSelect(prompt.text)}
type="button"
>
<div className="flex size-8 shrink-0 items-center justify-center rounded bg-accent/50">
<prompt.icon
className="size-4 text-foreground/60"
weight="duotone"
/>
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm">{prompt.text}</p>
<p className="text-foreground/50 text-xs">{prompt.category}</p>
</div>
<ArrowRightIcon className="size-4 shrink-0 text-transparent transition-all group-hover:text-foreground/50" />
</button>
))}
</div>
<div className="grid max-w-4xl gap-2 sm:grid-cols-2">
{SUGGESTED_PROMPTS.map((prompt) => (
<button
className={cn(
"group flex items-start gap-3 rounded border border-dashed p-3 text-left",
"transition-all hover:border-solid hover:bg-accent/30",
"disabled:cursor-not-allowed disabled:opacity-50"
)}
key={prompt.text}
onClick={() => onPromptSelect(prompt.text)}
type="button"
>
<div className="flex size-8 shrink-0 items-center justify-center rounded bg-accent/50">
<prompt.icon
className="size-4 text-foreground/60"
weight="duotone"
/>
</div>
<div className="min-w-0 flex-1">
<p className="text-sm">{prompt.text}</p>
<p className="text-foreground/50 text-xs">{prompt.category}</p>
</div>
<ArrowRightIcon className="size-4 shrink-0 text-transparent transition-all group-hover:text-foreground/50" />
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add weight="fill" to the arrow icon per coding guidelines.

As per coding guidelines, arrow icons should use weight="fill" rather than the default weight.

🔎 Proposed fix
-<ArrowRightIcon className="size-4 shrink-0 text-transparent transition-all group-hover:text-foreground/50" />
+<ArrowRightIcon className="size-4 shrink-0 text-transparent transition-all group-hover:text-foreground/50" weight="fill" />

Based on coding guidelines: "Use width='duotone' for most icons, use fill for arrows" (Note: the guideline likely means weight attribute, as that's the correct prop for phosphor-icons).

📝 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
<ArrowRightIcon className="size-4 shrink-0 text-transparent transition-all group-hover:text-foreground/50" />
<ArrowRightIcon className="size-4 shrink-0 text-transparent transition-all group-hover:text-foreground/50" weight="fill" />
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/websites/[id]/agent/_components/agent-page-content.tsx
around line 190, the ArrowRightIcon is missing the required weight="fill" prop
per coding guidelines; update the JSX element to include weight="fill" on the
ArrowRightIcon (e.g., add weight="fill" to its props) so the icon uses the
filled arrow style.

</button>
))}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function useAgentCommands() {
const [_, setInput] = useAtom(agentInputAtom);
const [showCommands, setShowCommands] = useAtom(showCommandsAtom);
const [commandQuery, setCommandQuery] = useAtom(commandQueryAtom);
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);

const { sendMessage } = useChat();

Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';
import type { ComponentProps } from 'react';
import { forwardRef, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';

type TextareaProps = ComponentProps<'textarea'> & {
type TextareaProps = ComponentProps<typeof TextareaAutosize> & {
showFocusIndicator?: boolean;
wrapperClassName?: string;
};
Expand Down Expand Up @@ -37,7 +38,7 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(

return (
<div className={cn('relative flex-1 min-w-0', wrapperClassName)}>
<textarea
<TextareaAutosize
ref={ref}
className={cn(
'field-sizing-content flex min-h-16 w-full rounded-sm border border-accent-brighter px-3 py-2 text-sm outline-none transition-all placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"react-image-crop": "^11.0.10",
"react-leaflet": "^5.0.0",
"react-resizable-panels": "^3.0.6",
"react-textarea-autosize": "^8.5.9",
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 | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

react-textarea-autosize latest version npm security vulnerabilities 8.5.9

💡 Result:

Short answer:

  • Latest published version: 8.5.9 (npm). [1]
  • Security status: no known direct vulnerabilities reported by Snyk/npm advisories for v8.5.9. Some third‑party scanners (e.g., ReversingLabs/Spectra) flag a medium‑risk finding — review their report if you rely on that feed. [2][3]

Sources:
[1] npm package page for react-textarea-autosize (v8.5.9).
[2] Snyk vulnerability page for react-textarea-autosize (no known security issues).
[3] ReversingLabs / Spectra report (flags a medium severity issue for v8.5.9).


Review ReversingLabs/Spectra security flag for [email protected].

While 8.5.9 is the latest stable version and has no known vulnerabilities in npm/Snyk, ReversingLabs/Spectra reports a medium-risk issue for this version. Investigate and address this third-party security finding.

🤖 Prompt for AI Agents
In apps/dashboard/package.json around line 77, the dependency
react-textarea-autosize@^8.5.9 was flagged by ReversingLabs/Spectra as
medium-risk; investigate by checking ReversingLabs details and comparing with
npm/Snyk advisories, then either upgrade to a non-flagged upstream version (or
apply a vetted patch/replace the package) and update package.json accordingly,
run npm install and npm audit/lockfile update, and add a short comment in
package.json or the PR describing the decision and evidence (or add a
packageResolution/override if immediate remediation is needed).

"recharts": "^2.15.4",
"shiki": "^3.20.0",
"simple-icons": "^16.2.0",
Expand Down
9 changes: 9 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
"react-image-crop": "^11.0.10",
"react-leaflet": "^5.0.0",
"react-resizable-panels": "^3.0.6",
"react-textarea-autosize": "^8.5.9",
"recharts": "^2.15.4",
"shiki": "^3.20.0",
"simple-icons": "^16.2.0",
Expand Down Expand Up @@ -3246,6 +3247,8 @@

"react-style-singleton": ["[email protected]", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],

"react-textarea-autosize": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="],

"react-transition-group": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],

"readable-stream": ["[email protected]", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
Expand Down Expand Up @@ -3672,6 +3675,12 @@

"use-callback-ref": ["[email protected]", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],

"use-composed-ref": ["[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w=="],

"use-isomorphic-layout-effect": ["[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="],

"use-latest": ["[email protected]", "", { "dependencies": { "use-isomorphic-layout-effect": "^1.1.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ=="],

"use-sidecar": ["[email protected]", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],

"use-sound": ["[email protected]", "", { "dependencies": { "howler": "^2.2.4" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-MNHT3FFC5HxNCrgZtrnpIMJI2cw/0D2xismcrtyht8BTuF5FhFhb57xO/jlQr2xJaFrc/0btzRQvGyHQwB7PVA=="],
Expand Down
Loading