├── app
├── favicon.ico
├── callback
│ └── route.ts
├── login
│ └── route.ts
├── ConvexClientProvider.tsx
├── page.tsx
├── layout.tsx
├── globals.css
└── api
│ └── analyze
│ └── route.ts
├── postcss.config.mjs
├── public
├── vercel.svg
├── window.svg
├── file.svg
├── globe.svg
└── next.svg
├── sampleData.jsonl
├── next.config.ts
├── lib
└── utils.ts
├── convex
├── convex.config.ts
├── _generated
│ ├── api.js
│ ├── dataModel.d.ts
│ ├── server.js
│ ├── server.d.ts
│ └── api.d.ts
├── tsconfig.json
├── README.md
└── agent.ts
├── middleware.ts
├── components
├── ui
│ ├── skeleton.tsx
│ ├── separator.tsx
│ ├── textarea.tsx
│ ├── input.tsx
│ ├── badge.tsx
│ ├── tooltip.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── sheet.tsx
│ └── sidebar.tsx
├── thinking-card.tsx
├── chat-input.tsx
├── header.tsx
├── threads.tsx
├── chat.tsx
└── chat-messages.tsx
├── eslint.config.mjs
├── components.json
├── @types
└── chat.ts
├── hooks
└── use-mobile.ts
├── .gitignore
├── tsconfig.json
├── package.json
├── solution
└── index.ts
└── README.md
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kulkarniankita/ai-agent-assistant-saas/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sampleData.jsonl:
--------------------------------------------------------------------------------
1 | {"text": "Buy groceries", "isCompleted": true}
2 | {"text": "Go for a swim", "isCompleted": true}
3 | {"text": "Integrate Convex", "isCompleted": false}
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | };
6 |
7 | export default nextConfig;
8 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/convex/convex.config.ts:
--------------------------------------------------------------------------------
1 | // convex/convex.config.ts
2 | import { defineApp } from "convex/server";
3 | import agent from "@convex-dev/agent/convex.config";
4 |
5 | const app = defineApp();
6 | app.use(agent);
7 |
8 | export default app;
9 |
--------------------------------------------------------------------------------
/app/callback/route.ts:
--------------------------------------------------------------------------------
1 | import { handleAuth } from "@workos-inc/authkit-nextjs";
2 |
3 | // Redirect the user to `/` after successful sign in
4 | // The redirect can be customized: `handleAuth({ returnPathname: '/foo' })`
5 | export const GET = handleAuth();
6 |
--------------------------------------------------------------------------------
/app/login/route.ts:
--------------------------------------------------------------------------------
1 | import { getSignInUrl } from "@workos-inc/authkit-nextjs";
2 | import { redirect } from "next/navigation";
3 |
4 | export const GET = async () => {
5 | const signInUrl = await getSignInUrl();
6 |
7 | return redirect(signInUrl);
8 | };
9 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { authkitMiddleware } from "@workos-inc/authkit-nextjs";
2 |
3 | export default authkitMiddleware();
4 |
5 | // Match against pages that require authentication
6 | // Leave this out if you want authentication on every page in your application
7 | // export const config = { matcher: ['/'] };
8 |
--------------------------------------------------------------------------------
/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4 | return (
5 |
10 | )
11 | }
12 |
13 | export { Skeleton }
14 |
--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/ConvexClientProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ConvexProvider, ConvexReactClient } from "convex/react";
4 | import { ReactNode } from "react";
5 |
6 | const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
7 |
8 | export function ConvexClientProvider({ children }: { children: ReactNode }) {
9 | return {children} ;
10 | }
11 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Chat from "@/components/chat";
2 | import { withAuth } from "@workos-inc/authkit-nextjs";
3 |
4 | export default async function Home() {
5 | // If the user isn't signed in, they will be automatically redirected to AuthKit
6 | const { user } = await withAuth();
7 |
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/components/thinking-card.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardContent } from "./ui/card";
2 | import { BrainIcon } from "lucide-react";
3 |
4 | export default function ThinkingCard() {
5 | return (
6 |
7 |
8 |
9 |
10 | Thinking...
11 |
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/@types/chat.ts:
--------------------------------------------------------------------------------
1 | export type ToolResult =
2 | | {
3 | type: "analyze";
4 | formatted: string;
5 | tone: string;
6 | clarity: string;
7 | grammarIssues: string;
8 | rewrittenMessage: string;
9 | }
10 | | { type: "email"; recipient: string; subject: string; body: string }
11 | | { type: "social"; platform: "X" | "LinkedIn" | "BlueSky"; message: string };
12 |
13 | export type Message = {
14 | role: "user" | "assistant";
15 | content: string | { toolResults: ToolResult };
16 | };
17 |
--------------------------------------------------------------------------------
/convex/_generated/api.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated `api` utility.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import { anyApi, componentsGeneric } from "convex/server";
12 |
13 | /**
14 | * A utility for referencing Convex functions in your app's API.
15 | *
16 | * Usage:
17 | * ```js
18 | * const myFunctionReference = api.myModule.myFunction;
19 | * ```
20 | */
21 | export const api = anyApi;
22 | export const internal = anyApi;
23 | export const components = componentsGeneric();
24 |
--------------------------------------------------------------------------------
/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | const MOBILE_BREAKPOINT = 768
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(undefined)
7 |
8 | React.useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12 | }
13 | mql.addEventListener("change", onChange)
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15 | return () => mql.removeEventListener("change", onChange)
16 | }, [])
17 |
18 | return !!isMobile
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | function Separator({
9 | className,
10 | orientation = "horizontal",
11 | decorative = true,
12 | ...props
13 | }: React.ComponentProps) {
14 | return (
15 |
25 | )
26 | }
27 |
28 | export { Separator }
29 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6 | return (
7 |
15 | )
16 | }
17 |
18 | export { Textarea }
19 |
--------------------------------------------------------------------------------
/convex/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | /* This TypeScript project config describes the environment that
3 | * Convex functions run in and is used to typecheck them.
4 | * You can modify it, but some settings required to use Convex.
5 | */
6 | "compilerOptions": {
7 | /* These settings are not required by Convex and can be modified. */
8 | "allowJs": true,
9 | "strict": true,
10 | "moduleResolution": "Bundler",
11 | "jsx": "react-jsx",
12 | "skipLibCheck": true,
13 | "allowSyntheticDefaultImports": true,
14 |
15 | /* These compiler options are required by Convex */
16 | "target": "ESNext",
17 | "lib": ["ES2021", "dom"],
18 | "forceConsistentCasingInFileNames": true,
19 | "module": "ESNext",
20 | "isolatedModules": true,
21 | "noEmit": true
22 | },
23 | "include": ["./**/*"],
24 | "exclude": ["./_generated"]
25 | }
26 |
--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6 | return (
7 |
18 | )
19 | }
20 |
21 | export { Input }
22 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { IBM_Plex_Sans } from "next/font/google";
3 | import { AuthKitProvider } from "@workos-inc/authkit-nextjs/components";
4 | import "./globals.css";
5 | import Header from "@/components/header";
6 | import { withAuth } from "@workos-inc/authkit-nextjs";
7 | import { ConvexClientProvider } from "./ConvexClientProvider";
8 |
9 | const ibmPlexSans = IBM_Plex_Sans({
10 | variable: "--font-ibm-plex-sans",
11 | subsets: ["latin"],
12 | weight: ["400", "500", "600", "700"],
13 | });
14 |
15 | export const metadata: Metadata = {
16 | title: "Ankita's AI Assistant",
17 | description: "Ankita's AI Assistant",
18 | };
19 |
20 | export default async function RootLayout({
21 | children,
22 | }: Readonly<{
23 | children: React.ReactNode;
24 | }>) {
25 | const { user } = await withAuth();
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | Welcome to Your AI Assistant
34 |
35 |
36 | Your personal AI companion for productivity and assistance
37 |
38 |
39 |
40 | {children}
41 |
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border border-border/100 dark:border-border/20 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 gap-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground dark:shadow-sm",
13 | brand:
14 | "border-transparent bg-brand text-primary-foreground dark:shadow-sm",
15 | secondary:
16 | "border-transparent bg-secondary text-secondary-foreground dark:shadow-sm",
17 | destructive:
18 | "border-transparent bg-destructive text-destructive-foreground dark:shadow-sm",
19 | outline: "text-foreground",
20 | },
21 | size: {
22 | default: "px-2.5 py-1",
23 | sm: "px-1",
24 | },
25 | },
26 | defaultVariants: {
27 | variant: "default",
28 | size: "default",
29 | },
30 | },
31 | );
32 |
33 | export interface BadgeProps
34 | extends React.HTMLAttributes,
35 | VariantProps {}
36 |
37 | function Badge({ className, variant, size, ...props }: BadgeProps) {
38 | return (
39 |
44 | );
45 | }
46 |
47 | export { Badge, badgeVariants };
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-agent-saas-assistant",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@ai-sdk/anthropic": "^1.2.10",
13 | "@ai-sdk/openai": "^1.3.21",
14 | "@anthropic-ai/sdk": "^0.40.0",
15 | "@convex-dev/agent": "^0.0.13",
16 | "@hookform/resolvers": "^5.0.1",
17 | "@langchain/core": "^0.3.49",
18 | "@langchain/openai": "^0.5.7",
19 | "@prisma/client": "^6.6.0",
20 | "@radix-ui/react-dialog": "^1.1.12",
21 | "@radix-ui/react-label": "^2.1.5",
22 | "@radix-ui/react-select": "^2.2.3",
23 | "@radix-ui/react-separator": "^1.1.5",
24 | "@radix-ui/react-slot": "^1.2.2",
25 | "@radix-ui/react-tabs": "^1.1.10",
26 | "@radix-ui/react-tooltip": "^1.2.5",
27 | "@workos-inc/authkit-nextjs": "^2.3.2",
28 | "@workos-inc/node": "^7.48.0",
29 | "ai": "^4.3.13",
30 | "class-variance-authority": "^0.7.1",
31 | "clsx": "^2.1.1",
32 | "convex": "^1.24.0",
33 | "convex-helpers": "^0.1.85",
34 | "lucide-react": "^0.503.0",
35 | "next": "15.3.1",
36 | "next-themes": "^0.4.6",
37 | "openai": "^4.96.0",
38 | "react": "^19.0.0",
39 | "react-dom": "^19.0.0",
40 | "react-hook-form": "^7.56.1",
41 | "tailwind-merge": "^3.2.0",
42 | "zod": "^3.24.3"
43 | },
44 | "devDependencies": {
45 | "@eslint/eslintrc": "^3",
46 | "@tailwindcss/postcss": "^4",
47 | "@types/node": "^20",
48 | "@types/react": "^19",
49 | "@types/react-dom": "^19",
50 | "eslint": "^9",
51 | "eslint-config-next": "15.3.1",
52 | "prisma": "^6.6.0",
53 | "tailwindcss": "^4",
54 | "tw-animate-css": "^1.2.9",
55 | "typescript": "^5"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/convex/_generated/dataModel.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated data model types.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import { AnyDataModel } from "convex/server";
12 | import type { GenericId } from "convex/values";
13 |
14 | /**
15 | * No `schema.ts` file found!
16 | *
17 | * This generated code has permissive types like `Doc = any` because
18 | * Convex doesn't know your schema. If you'd like more type safety, see
19 | * https://docs.convex.dev/using/schemas for instructions on how to add a
20 | * schema file.
21 | *
22 | * After you change a schema, rerun codegen with `npx convex dev`.
23 | */
24 |
25 | /**
26 | * The names of all of your Convex tables.
27 | */
28 | export type TableNames = string;
29 |
30 | /**
31 | * The type of a document stored in Convex.
32 | */
33 | export type Doc = any;
34 |
35 | /**
36 | * An identifier for a document in Convex.
37 | *
38 | * Convex documents are uniquely identified by their `Id`, which is accessible
39 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
40 | *
41 | * Documents can be loaded using `db.get(id)` in query and mutation functions.
42 | *
43 | * IDs are just strings at runtime, but this type can be used to distinguish them from other
44 | * strings when type checking.
45 | */
46 | export type Id =
47 | GenericId;
48 |
49 | /**
50 | * A type describing your Convex data model.
51 | *
52 | * This type includes information about what tables you have, the type of
53 | * documents stored in those tables, and the indexes defined on them.
54 | *
55 | * This type is used to parameterize methods like `queryGeneric` and
56 | * `mutationGeneric` to make them type-safe.
57 | */
58 | export type DataModel = AnyDataModel;
59 |
--------------------------------------------------------------------------------
/components/chat-input.tsx:
--------------------------------------------------------------------------------
1 | import { Textarea } from "./ui/textarea";
2 | import { Button } from "./ui/button";
3 | import { Send } from "lucide-react";
4 |
5 | interface ChatInputProps {
6 | message: string;
7 | setMessage: (message: string) => void;
8 | sendMessage: () => void;
9 | isLoading: boolean;
10 | error: string | null;
11 | }
12 |
13 | export default function ChatInput({
14 | message,
15 | setMessage,
16 | sendMessage,
17 | isLoading,
18 | error,
19 | }: ChatInputProps) {
20 | return (
21 |
22 |
23 |
43 |
44 | {error && (
45 |
46 |
52 |
58 |
59 | {error}
60 |
61 | )}
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | )
19 | }
20 |
21 | function Tooltip({
22 | ...props
23 | }: React.ComponentProps) {
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | function TooltipTrigger({
32 | ...props
33 | }: React.ComponentProps) {
34 | return
35 | }
36 |
37 | function TooltipContent({
38 | className,
39 | sideOffset = 0,
40 | children,
41 | ...props
42 | }: React.ComponentProps) {
43 | return (
44 |
45 |
54 | {children}
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
62 |
--------------------------------------------------------------------------------
/components/header.tsx:
--------------------------------------------------------------------------------
1 | import { signOut } from "@workos-inc/authkit-nextjs";
2 | import { getSignUpUrl, getSignInUrl } from "@workos-inc/authkit-nextjs";
3 | import { Button } from "./ui/button";
4 | import { User } from "@workos-inc/node";
5 |
6 | export default async function Header({ user }: { user: User | null }) {
7 | // Retrieves the user from the session or returns `null` if no user is signed in
8 |
9 | const signUpUrl = await getSignUpUrl();
10 | const signInUrl = await getSignInUrl();
11 |
12 | return (
13 |
14 |
15 | AI Agent Assistant
16 |
17 |
18 | {user ? (
19 |
36 | ) : (
37 |
51 | )}
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "text-primary-foreground shadow-sm dark:hover:from-primary/80 hover:from-primary/70 dark:hover:to-primary/70 hover:to-primary/90 bg-linear-to-b from-primary/60 to-primary/100 dark:from-primary/100 dark:to-primary/70 border-t-primary",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
18 | glow: "glass-4 hover:glass-5 shadow-md",
19 | secondary:
20 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
21 | ghost: "hover:bg-accent hover:text-accent-foreground",
22 | link: "text-foreground underline-offset-4 hover:underline",
23 | },
24 | size: {
25 | default: "h-9 px-4 py-2",
26 | xs: "h-7 rounded-md px-2",
27 | sm: "h-8 rounded-md px-3 text-xs",
28 | lg: "h-10 rounded-md px-5",
29 | icon: "size-9",
30 | },
31 | },
32 | defaultVariants: {
33 | variant: "default",
34 | size: "default",
35 | },
36 | },
37 | );
38 |
39 | export interface ButtonProps
40 | extends React.ButtonHTMLAttributes,
41 | VariantProps {
42 | asChild?: boolean;
43 | }
44 |
45 | function Button({
46 | className,
47 | variant,
48 | size,
49 | asChild = false,
50 | ...props
51 | }: ButtonProps) {
52 | const Comp = asChild ? Slot : "button";
53 | return (
54 |
59 | );
60 | }
61 |
62 | export { Button, buttonVariants };
63 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Card({ className, ...props }: React.ComponentProps<"div">) {
6 | return (
7 |
15 | )
16 | }
17 |
18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19 | return (
20 |
28 | )
29 | }
30 |
31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32 | return (
33 |
38 | )
39 | }
40 |
41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42 | return (
43 |
48 | )
49 | }
50 |
51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52 | return (
53 |
61 | )
62 | }
63 |
64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65 | return (
66 |
71 | )
72 | }
73 |
74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75 | return (
76 |
81 | )
82 | }
83 |
84 | export {
85 | Card,
86 | CardHeader,
87 | CardFooter,
88 | CardTitle,
89 | CardAction,
90 | CardDescription,
91 | CardContent,
92 | }
93 |
--------------------------------------------------------------------------------
/convex/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your Convex functions directory!
2 |
3 | Write your Convex functions here.
4 | See https://docs.convex.dev/functions for more.
5 |
6 | A query function that takes two arguments looks like:
7 |
8 | ```ts
9 | // functions.js
10 | import { query } from "./_generated/server";
11 | import { v } from "convex/values";
12 |
13 | export const myQueryFunction = query({
14 | // Validators for arguments.
15 | args: {
16 | first: v.number(),
17 | second: v.string(),
18 | },
19 |
20 | // Function implementation.
21 | handler: async (ctx, args) => {
22 | // Read the database as many times as you need here.
23 | // See https://docs.convex.dev/database/reading-data.
24 | const documents = await ctx.db.query("tablename").collect();
25 |
26 | // Arguments passed from the client are properties of the args object.
27 | console.log(args.first, args.second);
28 |
29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data,
30 | // remove non-public properties, or create new objects.
31 | return documents;
32 | },
33 | });
34 | ```
35 |
36 | Using this query function in a React component looks like:
37 |
38 | ```ts
39 | const data = useQuery(api.functions.myQueryFunction, {
40 | first: 10,
41 | second: "hello",
42 | });
43 | ```
44 |
45 | A mutation function looks like:
46 |
47 | ```ts
48 | // functions.js
49 | import { mutation } from "./_generated/server";
50 | import { v } from "convex/values";
51 |
52 | export const myMutationFunction = mutation({
53 | // Validators for arguments.
54 | args: {
55 | first: v.string(),
56 | second: v.string(),
57 | },
58 |
59 | // Function implementation.
60 | handler: async (ctx, args) => {
61 | // Insert or modify documents in the database here.
62 | // Mutations can also read from the database like queries.
63 | // See https://docs.convex.dev/database/writing-data.
64 | const message = { body: args.first, author: args.second };
65 | const id = await ctx.db.insert("messages", message);
66 |
67 | // Optionally, return a value from your mutation.
68 | return await ctx.db.get(id);
69 | },
70 | });
71 | ```
72 |
73 | Using this mutation function in a React component looks like:
74 |
75 | ```ts
76 | const mutation = useMutation(api.functions.myMutationFunction);
77 | function handleButtonPress() {
78 | // fire and forget, the most common way to use mutations
79 | mutation({ first: "Hello!", second: "me" });
80 | // OR
81 | // use the result once the mutation has completed
82 | mutation({ first: "Hello!", second: "me" }).then((result) =>
83 | console.log(result),
84 | );
85 | }
86 | ```
87 |
88 | Use the Convex CLI to push your functions to a deployment. See everything
89 | the Convex CLI can do by running `npx convex -h` in your project root
90 | directory. To learn more, launch the docs with `npx convex docs`.
91 |
--------------------------------------------------------------------------------
/convex/agent.ts:
--------------------------------------------------------------------------------
1 | import { anthropic } from "@ai-sdk/anthropic";
2 | import { Agent } from "@convex-dev/agent";
3 | import { tool } from "ai";
4 | import { z } from "zod";
5 | import { components } from "../convex/_generated/api";
6 | import { v } from "convex/values";
7 | import { action } from "../convex/_generated/server";
8 |
9 | // System prompt that defines the AI assistant's behavior
10 | const systemPrompt = `You are an expert AI assistant for Ankita. Your goal is to help her complete her tasks. These tasks include writing emails, messages, social media posts, and blog posts.`;
11 |
12 | // Setup the Tools that the AI assistant can use
13 | const tools = {
14 | analyzeMessage: tool({
15 | description: `Analyze the given message and provide improvements in the following areas:
16 | 1. Formatting: Improve the message structure and formatting
17 | 2. Tone: Suggest appropriate tone adjustments
18 | 3. Clarity: Identify and fix clarity issues
19 | 4. Grammar: Point out any grammar issues and re-write the message based on these criterias and share it with me. Make sure to follow the tone and format of the message.`,
20 | parameters: z.object({
21 | formatted: z.string(),
22 | tone: z.string(),
23 | clarity: z.string(),
24 | grammarIssues: z.string(),
25 | rewrittenMessage: z.string(),
26 | type: z.literal("analyze"),
27 | }),
28 | execute: async (args) => {
29 | return args;
30 | },
31 | }),
32 | writeEmail: tool({
33 | description: `Write an email to the given recipient with the given subject and body. Make sure to follow a friendly and professional tone. Don't use -- in the email and avoid using complex words.`,
34 | parameters: z.object({
35 | recipient: z.string(),
36 | subject: z.string(),
37 | body: z.string(),
38 | type: z.literal("email"),
39 | }),
40 | execute: async (args) => {
41 | return args;
42 | },
43 | }),
44 | };
45 |
46 | // Define an agent similarly to the AI SDK
47 | const aiAgentAssistant = new Agent(components.agent, {
48 | chat: anthropic("claude-3-5-sonnet-20240620"),
49 | instructions: systemPrompt,
50 | tools,
51 | });
52 |
53 | // Agent Chat functions
54 | export const createAgentAssistantThread = action({
55 | args: {
56 | prompt: v.string(),
57 | userId: v.string(),
58 | threadId: v.optional(v.string()),
59 | },
60 | handler: async (ctx, args) => {
61 | let thread;
62 | let threadId = args.threadId;
63 |
64 | if (!threadId) {
65 | // Create new thread if no threadId provided
66 | const result = await aiAgentAssistant.createThread(ctx, {
67 | userId: args.userId,
68 | });
69 | thread = result.thread;
70 | threadId = result.threadId;
71 | } else {
72 | // Continue existing thread
73 | const result = await aiAgentAssistant.continueThread(ctx, {
74 | threadId: threadId,
75 | userId: args.userId,
76 | });
77 | thread = result.thread;
78 | }
79 |
80 | const result = await thread?.generateText({
81 | prompt: args.prompt,
82 | });
83 |
84 | return {
85 | threadId,
86 | text: result?.text,
87 | toolResults: result?.toolResults?.[0]?.result,
88 | };
89 | },
90 | });
91 |
--------------------------------------------------------------------------------
/solution/index.ts:
--------------------------------------------------------------------------------
1 | import { anthropic } from "@ai-sdk/anthropic";
2 | import { Agent } from "@convex-dev/agent";
3 | import { tool } from "ai";
4 | import { z } from "zod";
5 | import { components } from "../convex/_generated/api";
6 | import { v } from "convex/values";
7 | import { action } from "../convex/_generated/server";
8 |
9 | // System prompt that defines the AI assistant's behavior
10 | const systemPrompt = `You are an expert AI assistant for Ankita. Your goal is to help her complete her tasks. These tasks include writing emails, messages, social media posts, and blog posts.`;
11 |
12 | // Setup the Tools that the AI assistant can use
13 | const tools = {
14 | analyzeMessage: tool({
15 | description: `Analyze the given message and provide improvements in the following areas:
16 | 1. Formatting: Improve the message structure and formatting
17 | 2. Tone: Suggest appropriate tone adjustments
18 | 3. Clarity: Identify and fix clarity issues
19 | 4. Grammar: Point out any grammar issues and re-write the message based on these criterias and share it with me. Make sure to follow the tone and format of the message.`,
20 | parameters: z.object({
21 | formatted: z.string(),
22 | tone: z.string(),
23 | clarity: z.string(),
24 | grammarIssues: z.string(),
25 | rewrittenMessage: z.string(),
26 | type: z.literal("analyze"),
27 | }),
28 | execute: async (args) => {
29 | return args;
30 | },
31 | }),
32 | writeEmail: tool({
33 | description: `Write an email to the given recipient with the given subject and body. Make sure to follow a friendly and professional tone. Don't use -- in the email and avoid using complex words.`,
34 | parameters: z.object({
35 | recipient: z.string(),
36 | subject: z.string(),
37 | body: z.string(),
38 | type: z.literal("email"),
39 | }),
40 | execute: async (args) => {
41 | return args;
42 | },
43 | }),
44 | };
45 |
46 | // Define an agent similarly to the AI SDK
47 | const aiAgentAssistant = new Agent(components.agent, {
48 | chat: anthropic("claude-3-5-sonnet-20240620"),
49 | instructions: systemPrompt,
50 | tools,
51 | });
52 |
53 | // Agent Chat functions
54 | export const createAgentAssistantThread = action({
55 | args: {
56 | prompt: v.string(),
57 | userId: v.string(),
58 | threadId: v.optional(v.string()),
59 | },
60 | handler: async (ctx, args) => {
61 | let thread;
62 | let threadId = args.threadId;
63 |
64 | if (!threadId) {
65 | // Create new thread if no threadId provided
66 | const result = await aiAgentAssistant.createThread(ctx, {
67 | userId: args.userId,
68 | });
69 | thread = result.thread;
70 | threadId = result.threadId;
71 | } else {
72 | // Continue existing thread
73 | const result = await aiAgentAssistant.continueThread(ctx, {
74 | threadId: threadId,
75 | userId: args.userId,
76 | });
77 | thread = result.thread;
78 | }
79 |
80 | const result = await thread?.generateText({
81 | prompt: args.prompt,
82 | });
83 |
84 | return {
85 | threadId,
86 | text: result?.text,
87 | toolResults: result?.toolResults?.[0]?.result,
88 | };
89 | },
90 | });
91 |
--------------------------------------------------------------------------------
/components/threads.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useQuery } from "convex/react";
4 | import { api } from "@/convex/_generated/api";
5 | import { Card, CardContent } from "@/components/ui/card";
6 | import { Clock, MessageSquare } from "lucide-react";
7 |
8 | interface Thread {
9 | _id: string;
10 | _creationTime: number;
11 | title?: string;
12 | summary?: string;
13 | status: "active" | "archived";
14 | }
15 |
16 | export default function Threads({
17 | user,
18 | onSelectThread,
19 | }: {
20 | user: { id: string } | null;
21 | onSelectThread: (threadId: string) => void;
22 | }) {
23 | // Query to get threads for the current user
24 | const threads = useQuery(api.agent.getThreadsByUserId, {
25 | userId: user?.id || "",
26 | paginationOpts: {
27 | numItems: 10,
28 | cursor: null,
29 | },
30 | });
31 |
32 | const formatDate = (timestamp: number) => {
33 | return new Date(timestamp).toLocaleString();
34 | };
35 |
36 | if (!user) {
37 | return (
38 |
39 |
40 | Please sign in to view your conversations
41 |
42 |
43 | );
44 | }
45 |
46 | if (threads === undefined) {
47 | return (
48 |
49 |
50 |
Loading conversations...
51 |
52 |
53 | );
54 | }
55 |
56 | return (
57 |
58 |
59 | Past Conversations
60 |
61 |
62 | {threads.page.map((thread: Thread) => (
63 |
onSelectThread(thread._id)}
67 | >
68 |
69 |
70 |
71 |
72 | {thread.title || "Untitled Conversation"}
73 |
74 | {thread.summary && (
75 |
76 | {thread.summary}
77 |
78 | )}
79 |
80 |
81 |
82 | {formatDate(thread._creationTime)}
83 |
84 |
85 |
86 |
87 |
88 | {thread.status === "active" ? "Active" : "Archived"}
89 |
90 |
91 |
92 |
93 | ))}
94 | {threads.page.length === 0 && (
95 |
96 |
No conversations yet
97 |
98 | Start a new conversation to see it here
99 |
100 |
101 | )}
102 |
103 |
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/convex/_generated/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated utilities for implementing server-side Convex query and mutation functions.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import {
12 | actionGeneric,
13 | httpActionGeneric,
14 | queryGeneric,
15 | mutationGeneric,
16 | internalActionGeneric,
17 | internalMutationGeneric,
18 | internalQueryGeneric,
19 | componentsGeneric,
20 | } from "convex/server";
21 |
22 | /**
23 | * Define a query in this Convex app's public API.
24 | *
25 | * This function will be allowed to read your Convex database and will be accessible from the client.
26 | *
27 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
28 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
29 | */
30 | export const query = queryGeneric;
31 |
32 | /**
33 | * Define a query that is only accessible from other Convex functions (but not from the client).
34 | *
35 | * This function will be allowed to read from your Convex database. It will not be accessible from the client.
36 | *
37 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
38 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
39 | */
40 | export const internalQuery = internalQueryGeneric;
41 |
42 | /**
43 | * Define a mutation in this Convex app's public API.
44 | *
45 | * This function will be allowed to modify your Convex database and will be accessible from the client.
46 | *
47 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
48 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
49 | */
50 | export const mutation = mutationGeneric;
51 |
52 | /**
53 | * Define a mutation that is only accessible from other Convex functions (but not from the client).
54 | *
55 | * This function will be allowed to modify your Convex database. It will not be accessible from the client.
56 | *
57 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
58 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
59 | */
60 | export const internalMutation = internalMutationGeneric;
61 |
62 | /**
63 | * Define an action in this Convex app's public API.
64 | *
65 | * An action is a function which can execute any JavaScript code, including non-deterministic
66 | * code and code with side-effects, like calling third-party services.
67 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
68 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
69 | *
70 | * @param func - The action. It receives an {@link ActionCtx} as its first argument.
71 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
72 | */
73 | export const action = actionGeneric;
74 |
75 | /**
76 | * Define an action that is only accessible from other Convex functions (but not from the client).
77 | *
78 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
79 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
80 | */
81 | export const internalAction = internalActionGeneric;
82 |
83 | /**
84 | * Define a Convex HTTP action.
85 | *
86 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
87 | * as its second.
88 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
89 | */
90 | export const httpAction = httpActionGeneric;
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AI Communication Copilot
2 |
3 | An AI-powered communication assistant that helps improve your messages by providing formatting, tone, clarity, and grammar suggestions.
4 |
5 | Thanks to [WorkOs](https://dub.sh/workos-authkit) for kindly sponsoring this video.
6 |
7 | [Download the Cheatsheet for free](https://dub.sh/agent-copilot) that accompanies this Github Repo for all the links and visuals.
8 |
9 | [The solution code is here](/solution/index.ts)
10 |
11 | ## What This Project Covers
12 |
13 | - Building an AI agent with memory and context awareness
14 | - Implementing real-time message analysis and improvements
15 | - Creating a modern, responsive UI with Next.js and Shadcn
16 | - Setting up secure authentication with WorkOS AuthKit
17 | - Managing real-time state and data with Convex
18 | - Implementing AI tools for message analysis and email generation
19 | - Building type-safe APIs and validation with TypeScript and Zod
20 | - Deploying a production-ready application on Vercel
21 |
22 | ## 📚 What You'll Learn
23 |
24 | - 🚀 Next.js 15.3 with App Router – server components and API routes
25 | - ⚛️ React 19 – interactive UI components with hooks
26 | - 🤖 Claude AI (3.5 Sonnet) – intelligent message analysis and improvements
27 | - 🔐 WorkOS AuthKit – secure authentication and user management
28 | - 🧠 AI SDK – building AI agents with memory and tools
29 | - 🎨 Shadcn UI – beautiful, accessible components with Radix UI
30 | - 💾 Convex – reactive backend with real-time state management
31 | - 📜 TypeScript – type-safe development
32 | - 👀 Zod – schema validation for AI tools and inputs
33 | - 💅 Tailwind CSS v4 – modern utility-first styling
34 | - 🚀 Deployment on Vercel – production-ready setup
35 |
36 | ## Tech Stack
37 |
38 | - **Frontend**: Next.js 15.3 with App Router
39 | - **UI**: Shadcn UI components with Radix UI primitives
40 | - **AI**: Claude 3.5 Sonnet via Anthropic SDK
41 | - **Backend**: Convex for real-time data and state management
42 | - **Authentication**: WorkOS AuthKit
43 | - **Styling**: Tailwind CSS v4
44 | - **Type Safety**: TypeScript with Zod validation
45 | - **Development**: ESLint, Turbopack
46 |
47 | ## Getting Started
48 |
49 | 1. Clone the repository
50 | 2. Install dependencies:
51 |
52 | ```bash
53 | npm install
54 | ```
55 |
56 | 3. Set up environment variables:
57 | Create a `.env` file with the following variables:
58 |
59 | ```
60 | AUTH_SECRET="" # Added by `npx auth`. Read more: https://cli.authjs.dev
61 |
62 | # Deployment used by `npx convex dev`
63 |
64 | CONVEX_DEPLOYMENT=
65 |
66 | NEXT_PUBLIC_CONVEX_URL=
67 |
68 | DATABASE_URL=""
69 |
70 | # Workos
71 |
72 | WORKOS_API_KEY=''
73 | WORKOS_CLIENT_ID=''
74 | WORKOS_COOKIE_PASSWORD="" # use npx secret to generate
75 |
76 | # configured in the WorkOS dashboard
77 |
78 | NEXT_PUBLIC_WORKOS_REDIRECT_URI="" # Most likely http://localhost:3000/callback
79 |
80 | # Claude
81 |
82 | ANTHROPIC_API_KEY=""
83 |
84 | ```
85 |
86 | 4. Run the development server:
87 |
88 | ```bash
89 | npm run dev
90 | ```
91 |
92 | 5. Open [http://localhost:3000](http://localhost:3000) in your browser
93 |
94 | ## Project Structure
95 |
96 | - `app/` - Next.js app router pages and API routes
97 | - `components/` - Reusable UI components
98 | - `lib/` - Utility functions and configurations
99 | - `public/` - Static assets
100 |
101 | ## Contributing
102 |
103 | 1. Fork the repository
104 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
105 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
106 | 4. Push to the branch (`git push origin feature/amazing-feature`)
107 | 5. Open a Pull Request
108 |
109 | ## License
110 |
111 | This project is licensed under the MIT License - see the LICENSE file for details.
112 |
--------------------------------------------------------------------------------
/components/chat.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Message, ToolResult } from "@/@types/chat";
4 | import { api } from "@/convex/_generated/api";
5 | import { User } from "@workos-inc/node";
6 | import { useAction } from "convex/react";
7 | import { useEffect, useRef, useState } from "react";
8 | import ChatInput from "./chat-input";
9 | import ChatMessages from "./chat-messages";
10 | import ThinkingCard from "./thinking-card";
11 |
12 | export default function Chat({ user }: { user: User | null }) {
13 | const [message, setMessage] = useState("");
14 | const [messages, setMessages] = useState([]);
15 | const [isLoading, setIsLoading] = useState(false);
16 | const [error, setError] = useState(null);
17 | const [threadId, setThreadId] = useState(null);
18 | const messagesEndRef = useRef(null);
19 |
20 | const scrollToBottom = () => {
21 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
22 | };
23 |
24 | useEffect(() => {
25 | scrollToBottom();
26 | }, [messages, isLoading]);
27 |
28 | const createAgentAssistantThreadAction = useAction(
29 | api.agent.createAgentAssistantThread
30 | );
31 |
32 | const sendMessage = async () => {
33 | if (!message.trim()) return;
34 |
35 | // Add user message to chat
36 | const userMessage: Message = { role: "user", content: message };
37 | setMessages((prev) => [...prev, userMessage]);
38 | setMessage(""); // Clear input
39 |
40 | setIsLoading(true);
41 | setError(null);
42 | try {
43 | // We invoke the AI Agent here
44 | const data = await createAgentAssistantThreadAction({
45 | prompt: message,
46 | userId: user?.id || "",
47 | threadId: threadId || undefined,
48 | });
49 | console.log("API Response:", data);
50 |
51 | // Store threadId for future messages
52 | if (data.threadId) {
53 | setThreadId(data.threadId);
54 | }
55 |
56 | let formattedContent: ToolResult | null = null;
57 |
58 | if (data?.toolResults) {
59 | const toolResult = data.toolResults as unknown as ToolResult;
60 | console.log("Tool Result:", toolResult);
61 |
62 | if (toolResult.type === "email") {
63 | const aiMessage: Message = {
64 | role: "assistant",
65 | content: { toolResults: toolResult },
66 | };
67 | setMessages((prev) => [...prev, aiMessage]);
68 | return;
69 | }
70 |
71 | if (toolResult.type === "analyze") {
72 | formattedContent = toolResult;
73 | }
74 | }
75 |
76 | // Add AI response to chat
77 | const aiMessage: Message = {
78 | role: "assistant",
79 | content: formattedContent
80 | ? { toolResults: formattedContent }
81 | : data.text || "",
82 | };
83 |
84 | setMessages((prev) => [...prev, aiMessage]);
85 | } catch (error) {
86 | setError("Failed to get response. Please try again.");
87 | console.error("Error getting response:", error);
88 | } finally {
89 | setIsLoading(false);
90 | }
91 | };
92 |
93 | return (
94 |
95 |
96 | {/* Chat Messages */}
97 |
98 |
99 | {messages.map((msg, index) => (
100 |
101 | ))}
102 | {isLoading &&
}
103 |
104 |
105 |
106 |
107 | {/* Input Area */}
108 |
109 |
116 |
117 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { XIcon } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | function Sheet({ ...props }: React.ComponentProps) {
10 | return
11 | }
12 |
13 | function SheetTrigger({
14 | ...props
15 | }: React.ComponentProps) {
16 | return
17 | }
18 |
19 | function SheetClose({
20 | ...props
21 | }: React.ComponentProps) {
22 | return
23 | }
24 |
25 | function SheetPortal({
26 | ...props
27 | }: React.ComponentProps) {
28 | return
29 | }
30 |
31 | function SheetOverlay({
32 | className,
33 | ...props
34 | }: React.ComponentProps) {
35 | return (
36 |
44 | )
45 | }
46 |
47 | function SheetContent({
48 | className,
49 | children,
50 | side = "right",
51 | ...props
52 | }: React.ComponentProps & {
53 | side?: "top" | "right" | "bottom" | "left"
54 | }) {
55 | return (
56 |
57 |
58 |
74 | {children}
75 |
76 |
77 | Close
78 |
79 |
80 |
81 | )
82 | }
83 |
84 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
85 | return (
86 |
91 | )
92 | }
93 |
94 | function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
95 | return (
96 |
101 | )
102 | }
103 |
104 | function SheetTitle({
105 | className,
106 | ...props
107 | }: React.ComponentProps) {
108 | return (
109 |
114 | )
115 | }
116 |
117 | function SheetDescription({
118 | className,
119 | ...props
120 | }: React.ComponentProps) {
121 | return (
122 |
127 | )
128 | }
129 |
130 | export {
131 | Sheet,
132 | SheetTrigger,
133 | SheetClose,
134 | SheetContent,
135 | SheetHeader,
136 | SheetFooter,
137 | SheetTitle,
138 | SheetDescription,
139 | }
140 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 | @import "tw-animate-css";
3 |
4 | @custom-variant dark (&:is(.dark *));
5 |
6 | @theme inline {
7 | --color-background: var(--background);
8 | --color-foreground: var(--foreground);
9 | --font-sans: var(--font-geist-sans);
10 | --font-mono: var(--font-geist-mono);
11 | --color-sidebar-ring: var(--sidebar-ring);
12 | --color-sidebar-border: var(--sidebar-border);
13 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14 | --color-sidebar-accent: var(--sidebar-accent);
15 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16 | --color-sidebar-primary: var(--sidebar-primary);
17 | --color-sidebar-foreground: var(--sidebar-foreground);
18 | --color-sidebar: var(--sidebar);
19 | --color-chart-5: var(--chart-5);
20 | --color-chart-4: var(--chart-4);
21 | --color-chart-3: var(--chart-3);
22 | --color-chart-2: var(--chart-2);
23 | --color-chart-1: var(--chart-1);
24 | --color-ring: var(--ring);
25 | --color-input: var(--input);
26 | --color-border: var(--border);
27 | --color-destructive: var(--destructive);
28 | --color-accent-foreground: var(--accent-foreground);
29 | --color-accent: var(--accent);
30 | --color-muted-foreground: var(--muted-foreground);
31 | --color-muted: var(--muted);
32 | --color-secondary-foreground: var(--secondary-foreground);
33 | --color-secondary: var(--secondary);
34 | --color-primary-foreground: var(--primary-foreground);
35 | --color-primary: var(--primary);
36 | --color-popover-foreground: var(--popover-foreground);
37 | --color-popover: var(--popover);
38 | --color-card-foreground: var(--card-foreground);
39 | --color-card: var(--card);
40 | --radius-sm: calc(var(--radius) - 4px);
41 | --radius-md: calc(var(--radius) - 2px);
42 | --radius-lg: var(--radius);
43 | --radius-xl: calc(var(--radius) + 4px);
44 | }
45 |
46 | @layer base {
47 | * {
48 | }
49 | body {
50 | @apply bg-background text-gray-900;
51 | background-image: radial-gradient(
52 | circle at 1px 1px,
53 | var(--border) 1px,
54 | transparent 0
55 | );
56 | background-size: 24px 24px;
57 | }
58 | }
59 |
60 | :root {
61 | --radius: 0.625rem;
62 | --background: oklch(1 0 0);
63 | --foreground: oklch(0.145 0 0);
64 | --card: oklch(1 0 0);
65 | --card-foreground: oklch(0.145 0 0);
66 | --popover: oklch(1 0 0);
67 | --popover-foreground: oklch(0.145 0 0);
68 | --primary: oklch(0.205 0 0);
69 | --primary-foreground: oklch(0.985 0 0);
70 | --secondary: oklch(0.97 0 0);
71 | --secondary-foreground: oklch(0.205 0 0);
72 | --muted: oklch(0.97 0 0);
73 | --muted-foreground: oklch(0.556 0 0);
74 | --accent: oklch(0.97 0 0);
75 | --accent-foreground: oklch(0.205 0 0);
76 | --destructive: oklch(0.577 0.245 27.325);
77 | --border: oklch(0.922 0 0);
78 | --input: oklch(0.922 0 0);
79 | --ring: oklch(0.708 0 0);
80 | --chart-1: oklch(0.646 0.222 41.116);
81 | --chart-2: oklch(0.6 0.118 184.704);
82 | --chart-3: oklch(0.398 0.07 227.392);
83 | --chart-4: oklch(0.828 0.189 84.429);
84 | --chart-5: oklch(0.769 0.188 70.08);
85 | --sidebar: oklch(0.985 0 0);
86 | --sidebar-foreground: oklch(0.145 0 0);
87 | --sidebar-primary: oklch(0.205 0 0);
88 | --sidebar-primary-foreground: oklch(0.985 0 0);
89 | --sidebar-accent: oklch(0.97 0 0);
90 | --sidebar-accent-foreground: oklch(0.205 0 0);
91 | --sidebar-border: oklch(0.922 0 0);
92 | --sidebar-ring: oklch(0.708 0 0);
93 | }
94 |
95 | .dark {
96 | --background: oklch(0.145 0 0);
97 | --foreground: oklch(0.985 0 0);
98 | --card: oklch(0.205 0 0);
99 | --card-foreground: oklch(0.985 0 0);
100 | --popover: oklch(0.205 0 0);
101 | --popover-foreground: oklch(0.985 0 0);
102 | --primary: oklch(0.922 0 0);
103 | --primary-foreground: oklch(0.205 0 0);
104 | --secondary: oklch(0.269 0 0);
105 | --secondary-foreground: oklch(0.985 0 0);
106 | --muted: oklch(0.269 0 0);
107 | --muted-foreground: oklch(0.708 0 0);
108 | --accent: oklch(0.269 0 0);
109 | --accent-foreground: oklch(0.985 0 0);
110 | --destructive: oklch(0.704 0.191 22.216);
111 | --border: oklch(1 0 0 / 10%);
112 | --input: oklch(1 0 0 / 15%);
113 | --ring: oklch(0.556 0 0);
114 | --chart-1: oklch(0.488 0.243 264.376);
115 | --chart-2: oklch(0.696 0.17 162.48);
116 | --chart-3: oklch(0.769 0.188 70.08);
117 | --chart-4: oklch(0.627 0.265 303.9);
118 | --chart-5: oklch(0.645 0.246 16.439);
119 | --sidebar: oklch(0.205 0 0);
120 | --sidebar-foreground: oklch(0.985 0 0);
121 | --sidebar-primary: oklch(0.488 0.243 264.376);
122 | --sidebar-primary-foreground: oklch(0.985 0 0);
123 | --sidebar-accent: oklch(0.269 0 0);
124 | --sidebar-accent-foreground: oklch(0.985 0 0);
125 | --sidebar-border: oklch(1 0 0 / 10%);
126 | --sidebar-ring: oklch(0.556 0 0);
127 | }
128 |
129 | @layer base {
130 | * {
131 | @apply border-border outline-ring/50;
132 | }
133 | body {
134 | @apply font-sans bg-background text-foreground;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/components/chat-messages.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from "./ui/card";
2 | import { CardContent } from "./ui/card";
3 | import { HandHelping, HeartIcon, TextIcon, User, Bot } from "lucide-react";
4 | import { Message } from "@/@types/chat";
5 |
6 | export default function ChatMessages({
7 | msg,
8 | index,
9 | }: {
10 | msg: Message;
11 | index: number;
12 | }) {
13 | const isUserMessage = msg.role === "user";
14 |
15 | return (
16 |
20 |
25 | {isUserMessage ? (
26 |
27 | ) : (
28 |
29 | )}
30 |
31 |
32 |
39 |
40 | {typeof msg.content === "string" ? (
41 | {msg.content}
42 | ) : msg.content.toolResults.type === "analyze" ? (
43 |
44 |
45 | {msg.content.toolResults.formatted}
46 |
47 |
48 |
49 |
50 |
51 | Clarity
52 |
53 |
54 | {msg.content.toolResults.clarity}
55 |
56 |
57 |
58 |
59 |
60 | Tone
61 |
62 |
63 | {msg.content.toolResults.tone}
64 |
65 |
66 |
67 |
68 |
69 | Grammar
70 |
71 |
72 | {msg.content.toolResults.grammarIssues}
73 |
74 |
75 |
76 |
77 |
78 | Rewritten Message
79 |
80 |
81 | {msg.content.toolResults.rewrittenMessage}
82 |
83 |
84 |
85 | ) : msg.content.toolResults.type === "email" ? (
86 |
87 |
88 |
89 | Email Details
90 |
91 |
92 |
93 | To: {" "}
94 | {msg.content.toolResults.recipient}
95 |
96 |
97 | Subject: {" "}
98 | {msg.content.toolResults.subject}
99 |
100 |
101 |
102 |
103 |
104 | {msg.content.toolResults.body}
105 |
106 |
107 |
108 | ) : (
109 | {JSON.stringify(msg.content)}
110 | )}
111 |
112 |
113 |
114 | );
115 | }
116 |
--------------------------------------------------------------------------------
/convex/_generated/server.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated utilities for implementing server-side Convex query and mutation functions.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import {
12 | ActionBuilder,
13 | AnyComponents,
14 | HttpActionBuilder,
15 | MutationBuilder,
16 | QueryBuilder,
17 | GenericActionCtx,
18 | GenericMutationCtx,
19 | GenericQueryCtx,
20 | GenericDatabaseReader,
21 | GenericDatabaseWriter,
22 | FunctionReference,
23 | } from "convex/server";
24 | import type { DataModel } from "./dataModel.js";
25 |
26 | type GenericCtx =
27 | | GenericActionCtx
28 | | GenericMutationCtx
29 | | GenericQueryCtx;
30 |
31 | /**
32 | * Define a query in this Convex app's public API.
33 | *
34 | * This function will be allowed to read your Convex database and will be accessible from the client.
35 | *
36 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
37 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
38 | */
39 | export declare const query: QueryBuilder;
40 |
41 | /**
42 | * Define a query that is only accessible from other Convex functions (but not from the client).
43 | *
44 | * This function will be allowed to read from your Convex database. It will not be accessible from the client.
45 | *
46 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
47 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
48 | */
49 | export declare const internalQuery: QueryBuilder;
50 |
51 | /**
52 | * Define a mutation in this Convex app's public API.
53 | *
54 | * This function will be allowed to modify your Convex database and will be accessible from the client.
55 | *
56 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
57 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
58 | */
59 | export declare const mutation: MutationBuilder;
60 |
61 | /**
62 | * Define a mutation that is only accessible from other Convex functions (but not from the client).
63 | *
64 | * This function will be allowed to modify your Convex database. It will not be accessible from the client.
65 | *
66 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
67 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
68 | */
69 | export declare const internalMutation: MutationBuilder;
70 |
71 | /**
72 | * Define an action in this Convex app's public API.
73 | *
74 | * An action is a function which can execute any JavaScript code, including non-deterministic
75 | * code and code with side-effects, like calling third-party services.
76 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
77 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
78 | *
79 | * @param func - The action. It receives an {@link ActionCtx} as its first argument.
80 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
81 | */
82 | export declare const action: ActionBuilder;
83 |
84 | /**
85 | * Define an action that is only accessible from other Convex functions (but not from the client).
86 | *
87 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
88 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
89 | */
90 | export declare const internalAction: ActionBuilder;
91 |
92 | /**
93 | * Define an HTTP action.
94 | *
95 | * This function will be used to respond to HTTP requests received by a Convex
96 | * deployment if the requests matches the path and method where this action
97 | * is routed. Be sure to route your action in `convex/http.js`.
98 | *
99 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
100 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
101 | */
102 | export declare const httpAction: HttpActionBuilder;
103 |
104 | /**
105 | * A set of services for use within Convex query functions.
106 | *
107 | * The query context is passed as the first argument to any Convex query
108 | * function run on the server.
109 | *
110 | * This differs from the {@link MutationCtx} because all of the services are
111 | * read-only.
112 | */
113 | export type QueryCtx = GenericQueryCtx;
114 |
115 | /**
116 | * A set of services for use within Convex mutation functions.
117 | *
118 | * The mutation context is passed as the first argument to any Convex mutation
119 | * function run on the server.
120 | */
121 | export type MutationCtx = GenericMutationCtx;
122 |
123 | /**
124 | * A set of services for use within Convex action functions.
125 | *
126 | * The action context is passed as the first argument to any Convex action
127 | * function run on the server.
128 | */
129 | export type ActionCtx = GenericActionCtx;
130 |
131 | /**
132 | * An interface to read from the database within Convex query functions.
133 | *
134 | * The two entry points are {@link DatabaseReader.get}, which fetches a single
135 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts
136 | * building a query.
137 | */
138 | export type DatabaseReader = GenericDatabaseReader;
139 |
140 | /**
141 | * An interface to read from and write to the database within Convex mutation
142 | * functions.
143 | *
144 | * Convex guarantees that all writes within a single mutation are
145 | * executed atomically, so you never have to worry about partial writes leaving
146 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
147 | * for the guarantees Convex provides your functions.
148 | */
149 | export type DatabaseWriter = GenericDatabaseWriter;
150 |
--------------------------------------------------------------------------------
/app/api/analyze/route.ts:
--------------------------------------------------------------------------------
1 | import Anthropic from "@anthropic-ai/sdk";
2 | import { NextResponse } from "next/server";
3 |
4 | // Define types for request and response
5 | type RequestData = {
6 | message: string;
7 | };
8 |
9 | // Define response types
10 | type BaseResponse = {
11 | error?: string;
12 | details?: string;
13 | };
14 |
15 | // Response when returning plain text
16 | type TextResponse = BaseResponse & {
17 | text: string;
18 | };
19 |
20 | // Response when a tool is used
21 | type ToolResponse = BaseResponse & {
22 | tool: string;
23 | input: Record;
24 | tool_use_id: string;
25 | };
26 |
27 | // Create a singleton instance of the Anthropic client
28 | const anthropicClient = new Anthropic({
29 | apiKey: process.env.ANTHROPIC_API_KEY || "",
30 | });
31 |
32 | // Define the available tools
33 | const tools = [
34 | {
35 | name: "analyzeMessage",
36 | description: `Analyze the given message and provide improvements in the following areas:
37 | 1. Formatting: Improve the message structure and formatting
38 | 2. Tone: Suggest appropriate tone adjustments
39 | 3. Clarity: Identify and fix clarity issues
40 | 4. Grammar: Point out any grammar issues
41 |
42 | Provide your analysis in JSON format with these exact keys:
43 | {
44 | "formatted": "formatted message here",
45 | "tone": "tone analysis here",
46 | "clarity": "clarity improvements here",
47 | "grammarIssues": "grammar issues here"
48 | }`,
49 | input_schema: {
50 | type: "object",
51 | properties: {
52 | formatted: {
53 | type: "string",
54 | description: "Formatted message",
55 | },
56 | tone: {
57 | type: "string",
58 | description: "Tone analysis",
59 | },
60 | clarity: {
61 | type: "string",
62 | description: "Clarity improvements",
63 | },
64 | grammarIssues: {
65 | type: "string",
66 | description: "Grammar issues",
67 | },
68 | },
69 | required: ["formatted", "tone", "clarity", "grammarIssues"],
70 | },
71 | },
72 | {
73 | name: "writeEmail",
74 | description: `Write an email to the given recipient with the given subject and body. Make sure to follow a friendly and professional tone. Don't use -- in the email and avoid using complex words.`,
75 | input_schema: {
76 | type: "object",
77 | properties: {
78 | recipient: {
79 | type: "string",
80 | description: "Recipient email address",
81 | },
82 | subject: { type: "string", description: "Subject of the email" },
83 | body: { type: "string", description: "Body of the email" },
84 | },
85 | required: ["recipient", "subject", "body"],
86 | },
87 | },
88 | {
89 | name: "writeSocialMediaPost",
90 | description: `Write a social media post for X, LinkedIn, and BlueSky. Make sure to follow the algorithm rules for each platform. Don't sound cringey.`,
91 | input_schema: {
92 | type: "object",
93 | properties: {
94 | platform: {
95 | type: "string",
96 | description:
97 | "Platform to write the post for (X, LinkedIn, or BlueSky)",
98 | enum: ["X", "LinkedIn", "BlueSky"],
99 | },
100 | message: { type: "string", description: "Message" },
101 | },
102 | required: ["platform", "message"],
103 | },
104 | },
105 | ];
106 |
107 | // System prompt that defines the AI assistant's behavior
108 | const systemPrompt = `You are an expert AI assistant for Ankita. Your goal is to help her complete her tasks. These tasks include writing emails, messages, social media posts, and blog posts.
109 |
110 | When using the analyzeMessage tool, you MUST provide all required fields:
111 | - formatted: The improved version of the message
112 | - tone: Analysis of the message's tone and suggestions for improvement
113 | - clarity: Specific clarity improvements
114 | - grammarIssues: Any grammar issues found and their corrections
115 |
116 | Do not skip any of these fields when using the analyzeMessage tool.`;
117 |
118 | export async function POST(request: Request) {
119 | try {
120 | // Validate request data
121 | const data = (await request.json()) as Partial;
122 |
123 | if (!data.message) {
124 | return NextResponse.json(
125 | { error: "Message is required" },
126 | { status: 400 }
127 | );
128 | }
129 |
130 | // Check if API key is configured
131 | if (!process.env.ANTHROPIC_API_KEY) {
132 | console.error("Missing Anthropic API key");
133 | return NextResponse.json(
134 | { error: "Server configuration error" },
135 | { status: 500 }
136 | );
137 | }
138 |
139 | // Call Anthropic API
140 | const response = await anthropicClient.messages.create({
141 | model: "claude-3-opus-20240229",
142 | max_tokens: 1000,
143 | temperature: 0.7,
144 | system: systemPrompt,
145 | messages: [
146 | {
147 | role: "user",
148 | content: data.message,
149 | },
150 | ],
151 | tools,
152 | });
153 |
154 | console.log("content", response.content);
155 |
156 | // Process Claude's response
157 | if (!response.content || response.content.length === 0) {
158 | return NextResponse.json(
159 | { error: "Empty response from Claude" },
160 | { status: 500 }
161 | );
162 | }
163 |
164 | return NextResponse.json(response.content);
165 | } catch (error) {
166 | // Enhanced error handling
167 | console.error("Error processing request:", error);
168 |
169 | // Determine appropriate status code based on error
170 | let statusCode = 500;
171 | let errorMessage = "Internal server error";
172 |
173 | if (error instanceof Error) {
174 | // Check for specific error types
175 | if (
176 | error.message.includes("rate limit") ||
177 | error.message.includes("quota")
178 | ) {
179 | statusCode = 429;
180 | errorMessage = "Rate limit exceeded";
181 | } else if (
182 | error.message.includes("unauthorized") ||
183 | error.message.includes("authentication")
184 | ) {
185 | statusCode = 401;
186 | errorMessage = "Authentication error";
187 | } else if (error.message.includes("invalid request")) {
188 | statusCode = 400;
189 | errorMessage = "Invalid request";
190 | } else if (error.message.includes("thinking")) {
191 | // Handle case where we received internal thinking output
192 | statusCode = 500;
193 | errorMessage =
194 | "Received internal thinking output instead of proper response";
195 | }
196 | }
197 |
198 | return NextResponse.json(
199 | {
200 | error: errorMessage,
201 | details: error instanceof Error ? error.message : "Unknown error",
202 | },
203 | { status: statusCode }
204 | );
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/components/ui/sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Slot } from "@radix-ui/react-slot"
5 | import { VariantProps, cva } from "class-variance-authority"
6 | import { PanelLeftIcon } from "lucide-react"
7 |
8 | import { useIsMobile } from "@/hooks/use-mobile"
9 | import { cn } from "@/lib/utils"
10 | import { Button } from "@/components/ui/button"
11 | import { Input } from "@/components/ui/input"
12 | import { Separator } from "@/components/ui/separator"
13 | import {
14 | Sheet,
15 | SheetContent,
16 | SheetDescription,
17 | SheetHeader,
18 | SheetTitle,
19 | } from "@/components/ui/sheet"
20 | import { Skeleton } from "@/components/ui/skeleton"
21 | import {
22 | Tooltip,
23 | TooltipContent,
24 | TooltipProvider,
25 | TooltipTrigger,
26 | } from "@/components/ui/tooltip"
27 |
28 | const SIDEBAR_COOKIE_NAME = "sidebar_state"
29 | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
30 | const SIDEBAR_WIDTH = "16rem"
31 | const SIDEBAR_WIDTH_MOBILE = "18rem"
32 | const SIDEBAR_WIDTH_ICON = "3rem"
33 | const SIDEBAR_KEYBOARD_SHORTCUT = "b"
34 |
35 | type SidebarContextProps = {
36 | state: "expanded" | "collapsed"
37 | open: boolean
38 | setOpen: (open: boolean) => void
39 | openMobile: boolean
40 | setOpenMobile: (open: boolean) => void
41 | isMobile: boolean
42 | toggleSidebar: () => void
43 | }
44 |
45 | const SidebarContext = React.createContext(null)
46 |
47 | function useSidebar() {
48 | const context = React.useContext(SidebarContext)
49 | if (!context) {
50 | throw new Error("useSidebar must be used within a SidebarProvider.")
51 | }
52 |
53 | return context
54 | }
55 |
56 | function SidebarProvider({
57 | defaultOpen = true,
58 | open: openProp,
59 | onOpenChange: setOpenProp,
60 | className,
61 | style,
62 | children,
63 | ...props
64 | }: React.ComponentProps<"div"> & {
65 | defaultOpen?: boolean
66 | open?: boolean
67 | onOpenChange?: (open: boolean) => void
68 | }) {
69 | const isMobile = useIsMobile()
70 | const [openMobile, setOpenMobile] = React.useState(false)
71 |
72 | // This is the internal state of the sidebar.
73 | // We use openProp and setOpenProp for control from outside the component.
74 | const [_open, _setOpen] = React.useState(defaultOpen)
75 | const open = openProp ?? _open
76 | const setOpen = React.useCallback(
77 | (value: boolean | ((value: boolean) => boolean)) => {
78 | const openState = typeof value === "function" ? value(open) : value
79 | if (setOpenProp) {
80 | setOpenProp(openState)
81 | } else {
82 | _setOpen(openState)
83 | }
84 |
85 | // This sets the cookie to keep the sidebar state.
86 | document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
87 | },
88 | [setOpenProp, open]
89 | )
90 |
91 | // Helper to toggle the sidebar.
92 | const toggleSidebar = React.useCallback(() => {
93 | return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
94 | }, [isMobile, setOpen, setOpenMobile])
95 |
96 | // Adds a keyboard shortcut to toggle the sidebar.
97 | React.useEffect(() => {
98 | const handleKeyDown = (event: KeyboardEvent) => {
99 | if (
100 | event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
101 | (event.metaKey || event.ctrlKey)
102 | ) {
103 | event.preventDefault()
104 | toggleSidebar()
105 | }
106 | }
107 |
108 | window.addEventListener("keydown", handleKeyDown)
109 | return () => window.removeEventListener("keydown", handleKeyDown)
110 | }, [toggleSidebar])
111 |
112 | // We add a state so that we can do data-state="expanded" or "collapsed".
113 | // This makes it easier to style the sidebar with Tailwind classes.
114 | const state = open ? "expanded" : "collapsed"
115 |
116 | const contextValue = React.useMemo(
117 | () => ({
118 | state,
119 | open,
120 | setOpen,
121 | isMobile,
122 | openMobile,
123 | setOpenMobile,
124 | toggleSidebar,
125 | }),
126 | [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
127 | )
128 |
129 | return (
130 |
131 |
132 |
147 | {children}
148 |
149 |
150 |
151 | )
152 | }
153 |
154 | function Sidebar({
155 | side = "left",
156 | variant = "sidebar",
157 | collapsible = "offcanvas",
158 | className,
159 | children,
160 | ...props
161 | }: React.ComponentProps<"div"> & {
162 | side?: "left" | "right"
163 | variant?: "sidebar" | "floating" | "inset"
164 | collapsible?: "offcanvas" | "icon" | "none"
165 | }) {
166 | const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
167 |
168 | if (collapsible === "none") {
169 | return (
170 |
178 | {children}
179 |
180 | )
181 | }
182 |
183 | if (isMobile) {
184 | return (
185 |
186 |
198 |
199 | Sidebar
200 | Displays the mobile sidebar.
201 |
202 | {children}
203 |
204 |
205 | )
206 | }
207 |
208 | return (
209 |
217 | {/* This is what handles the sidebar gap on desktop */}
218 |
229 |
244 |
249 | {children}
250 |
251 |
252 |
253 | )
254 | }
255 |
256 | function SidebarTrigger({
257 | className,
258 | onClick,
259 | ...props
260 | }: React.ComponentProps) {
261 | const { toggleSidebar } = useSidebar()
262 |
263 | return (
264 | {
271 | onClick?.(event)
272 | toggleSidebar()
273 | }}
274 | {...props}
275 | >
276 |
277 | Toggle Sidebar
278 |
279 | )
280 | }
281 |
282 | function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
283 | const { toggleSidebar } = useSidebar()
284 |
285 | return (
286 |
304 | )
305 | }
306 |
307 | function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
308 | return (
309 |
318 | )
319 | }
320 |
321 | function SidebarInput({
322 | className,
323 | ...props
324 | }: React.ComponentProps) {
325 | return (
326 |
332 | )
333 | }
334 |
335 | function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
336 | return (
337 |
343 | )
344 | }
345 |
346 | function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
347 | return (
348 |
354 | )
355 | }
356 |
357 | function SidebarSeparator({
358 | className,
359 | ...props
360 | }: React.ComponentProps) {
361 | return (
362 |
368 | )
369 | }
370 |
371 | function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
372 | return (
373 |
382 | )
383 | }
384 |
385 | function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
386 | return (
387 |
393 | )
394 | }
395 |
396 | function SidebarGroupLabel({
397 | className,
398 | asChild = false,
399 | ...props
400 | }: React.ComponentProps<"div"> & { asChild?: boolean }) {
401 | const Comp = asChild ? Slot : "div"
402 |
403 | return (
404 | svg]:size-4 [&>svg]:shrink-0",
409 | "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
410 | className
411 | )}
412 | {...props}
413 | />
414 | )
415 | }
416 |
417 | function SidebarGroupAction({
418 | className,
419 | asChild = false,
420 | ...props
421 | }: React.ComponentProps<"button"> & { asChild?: boolean }) {
422 | const Comp = asChild ? Slot : "button"
423 |
424 | return (
425 | svg]:size-4 [&>svg]:shrink-0",
430 | // Increases the hit area of the button on mobile.
431 | "after:absolute after:-inset-2 md:after:hidden",
432 | "group-data-[collapsible=icon]:hidden",
433 | className
434 | )}
435 | {...props}
436 | />
437 | )
438 | }
439 |
440 | function SidebarGroupContent({
441 | className,
442 | ...props
443 | }: React.ComponentProps<"div">) {
444 | return (
445 |
451 | )
452 | }
453 |
454 | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
455 | return (
456 |
462 | )
463 | }
464 |
465 | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
466 | return (
467 |
473 | )
474 | }
475 |
476 | const sidebarMenuButtonVariants = cva(
477 | "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
478 | {
479 | variants: {
480 | variant: {
481 | default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
482 | outline:
483 | "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
484 | },
485 | size: {
486 | default: "h-8 text-sm",
487 | sm: "h-7 text-xs",
488 | lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
489 | },
490 | },
491 | defaultVariants: {
492 | variant: "default",
493 | size: "default",
494 | },
495 | }
496 | )
497 |
498 | function SidebarMenuButton({
499 | asChild = false,
500 | isActive = false,
501 | variant = "default",
502 | size = "default",
503 | tooltip,
504 | className,
505 | ...props
506 | }: React.ComponentProps<"button"> & {
507 | asChild?: boolean
508 | isActive?: boolean
509 | tooltip?: string | React.ComponentProps
510 | } & VariantProps) {
511 | const Comp = asChild ? Slot : "button"
512 | const { isMobile, state } = useSidebar()
513 |
514 | const button = (
515 |
523 | )
524 |
525 | if (!tooltip) {
526 | return button
527 | }
528 |
529 | if (typeof tooltip === "string") {
530 | tooltip = {
531 | children: tooltip,
532 | }
533 | }
534 |
535 | return (
536 |
537 | {button}
538 |
544 |
545 | )
546 | }
547 |
548 | function SidebarMenuAction({
549 | className,
550 | asChild = false,
551 | showOnHover = false,
552 | ...props
553 | }: React.ComponentProps<"button"> & {
554 | asChild?: boolean
555 | showOnHover?: boolean
556 | }) {
557 | const Comp = asChild ? Slot : "button"
558 |
559 | return (
560 | svg]:size-4 [&>svg]:shrink-0",
565 | // Increases the hit area of the button on mobile.
566 | "after:absolute after:-inset-2 md:after:hidden",
567 | "peer-data-[size=sm]/menu-button:top-1",
568 | "peer-data-[size=default]/menu-button:top-1.5",
569 | "peer-data-[size=lg]/menu-button:top-2.5",
570 | "group-data-[collapsible=icon]:hidden",
571 | showOnHover &&
572 | "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
573 | className
574 | )}
575 | {...props}
576 | />
577 | )
578 | }
579 |
580 | function SidebarMenuBadge({
581 | className,
582 | ...props
583 | }: React.ComponentProps<"div">) {
584 | return (
585 |
599 | )
600 | }
601 |
602 | function SidebarMenuSkeleton({
603 | className,
604 | showIcon = false,
605 | ...props
606 | }: React.ComponentProps<"div"> & {
607 | showIcon?: boolean
608 | }) {
609 | // Random width between 50 to 90%.
610 | const width = React.useMemo(() => {
611 | return `${Math.floor(Math.random() * 40) + 50}%`
612 | }, [])
613 |
614 | return (
615 |
621 | {showIcon && (
622 |
626 | )}
627 |
636 |
637 | )
638 | }
639 |
640 | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
641 | return (
642 |
652 | )
653 | }
654 |
655 | function SidebarMenuSubItem({
656 | className,
657 | ...props
658 | }: React.ComponentProps<"li">) {
659 | return (
660 |
666 | )
667 | }
668 |
669 | function SidebarMenuSubButton({
670 | asChild = false,
671 | size = "md",
672 | isActive = false,
673 | className,
674 | ...props
675 | }: React.ComponentProps<"a"> & {
676 | asChild?: boolean
677 | size?: "sm" | "md"
678 | isActive?: boolean
679 | }) {
680 | const Comp = asChild ? Slot : "a"
681 |
682 | return (
683 | svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
690 | "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
691 | size === "sm" && "text-xs",
692 | size === "md" && "text-sm",
693 | "group-data-[collapsible=icon]:hidden",
694 | className
695 | )}
696 | {...props}
697 | />
698 | )
699 | }
700 |
701 | export {
702 | Sidebar,
703 | SidebarContent,
704 | SidebarFooter,
705 | SidebarGroup,
706 | SidebarGroupAction,
707 | SidebarGroupContent,
708 | SidebarGroupLabel,
709 | SidebarHeader,
710 | SidebarInput,
711 | SidebarInset,
712 | SidebarMenu,
713 | SidebarMenuAction,
714 | SidebarMenuBadge,
715 | SidebarMenuButton,
716 | SidebarMenuItem,
717 | SidebarMenuSkeleton,
718 | SidebarMenuSub,
719 | SidebarMenuSubButton,
720 | SidebarMenuSubItem,
721 | SidebarProvider,
722 | SidebarRail,
723 | SidebarSeparator,
724 | SidebarTrigger,
725 | useSidebar,
726 | }
727 |
--------------------------------------------------------------------------------
/convex/_generated/api.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated `api` utility.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import type * as agent from "../agent.js";
12 |
13 | import type {
14 | ApiFromModules,
15 | FilterApi,
16 | FunctionReference,
17 | } from "convex/server";
18 |
19 | /**
20 | * A utility for referencing Convex functions in your app's API.
21 | *
22 | * Usage:
23 | * ```js
24 | * const myFunctionReference = api.myModule.myFunction;
25 | * ```
26 | */
27 | declare const fullApi: ApiFromModules<{
28 | agent: typeof agent;
29 | }>;
30 | declare const fullApiWithMounts: typeof fullApi;
31 |
32 | export declare const api: FilterApi<
33 | typeof fullApiWithMounts,
34 | FunctionReference
35 | >;
36 | export declare const internal: FilterApi<
37 | typeof fullApiWithMounts,
38 | FunctionReference
39 | >;
40 |
41 | export declare const components: {
42 | agent: {
43 | messages: {
44 | addMessages: FunctionReference<
45 | "mutation",
46 | "internal",
47 | {
48 | agentName?: string;
49 | embeddings?: {
50 | dimension: 128 | 256 | 512 | 768 | 1024 | 1536 | 2048 | 3072 | 4096;
51 | model: string;
52 | vectors: Array | null>;
53 | };
54 | failPendingSteps?: boolean;
55 | messages: Array<{
56 | fileId?: string;
57 | id?: string;
58 | message:
59 | | {
60 | content:
61 | | string
62 | | Array<
63 | | {
64 | experimental_providerMetadata?: Record;
65 | providerOptions?: Record;
66 | text: string;
67 | type: "text";
68 | }
69 | | {
70 | experimental_providerMetadata?: Record;
71 | image: string | ArrayBuffer;
72 | mimeType?: string;
73 | providerOptions?: Record;
74 | type: "image";
75 | }
76 | | {
77 | data: string | ArrayBuffer;
78 | experimental_providerMetadata?: Record;
79 | mimeType: string;
80 | providerOptions?: Record;
81 | type: "file";
82 | }
83 | >;
84 | experimental_providerMetadata?: Record;
85 | providerOptions?: Record;
86 | role: "user";
87 | }
88 | | {
89 | content:
90 | | string
91 | | Array<
92 | | {
93 | experimental_providerMetadata?: Record;
94 | providerOptions?: Record;
95 | text: string;
96 | type: "text";
97 | }
98 | | {
99 | data: string | ArrayBuffer;
100 | experimental_providerMetadata?: Record;
101 | mimeType: string;
102 | providerOptions?: Record;
103 | type: "file";
104 | }
105 | | {
106 | experimental_providerMetadata?: Record;
107 | providerOptions?: Record;
108 | text: string;
109 | type: "reasoning";
110 | }
111 | | {
112 | data: string;
113 | experimental_providerMetadata?: Record;
114 | providerOptions?: Record;
115 | type: "redacted-reasoning";
116 | }
117 | | {
118 | args: any;
119 | experimental_providerMetadata?: Record;
120 | providerOptions?: Record;
121 | toolCallId: string;
122 | toolName: string;
123 | type: "tool-call";
124 | }
125 | >;
126 | experimental_providerMetadata?: Record;
127 | providerOptions?: Record;
128 | role: "assistant";
129 | }
130 | | {
131 | content: Array<{
132 | args?: any;
133 | experimental_content?: Array<
134 | | { text: string; type: "text" }
135 | | { data: string; mimeType?: string; type: "image" }
136 | >;
137 | experimental_providerMetadata?: Record;
138 | isError?: boolean;
139 | providerOptions?: Record;
140 | result: any;
141 | toolCallId: string;
142 | toolName: string;
143 | type: "tool-result";
144 | }>;
145 | experimental_providerMetadata?: Record;
146 | providerOptions?: Record;
147 | role: "tool";
148 | }
149 | | {
150 | content: string;
151 | experimental_providerMetadata?: Record;
152 | providerOptions?: Record;
153 | role: "system";
154 | };
155 | }>;
156 | model?: string;
157 | parentMessageId?: string;
158 | pending?: boolean;
159 | stepId?: string;
160 | threadId: string;
161 | userId?: string;
162 | },
163 | {
164 | messages: Array<{
165 | _creationTime: number;
166 | _id: string;
167 | agentName?: string;
168 | embeddingId?:
169 | | string
170 | | string
171 | | string
172 | | string
173 | | string
174 | | string
175 | | string
176 | | string
177 | | string;
178 | error?: string;
179 | fileId?: string;
180 | id?: string;
181 | message?:
182 | | {
183 | content:
184 | | string
185 | | Array<
186 | | {
187 | experimental_providerMetadata?: Record;
188 | providerOptions?: Record;
189 | text: string;
190 | type: "text";
191 | }
192 | | {
193 | experimental_providerMetadata?: Record;
194 | image: string | ArrayBuffer;
195 | mimeType?: string;
196 | providerOptions?: Record;
197 | type: "image";
198 | }
199 | | {
200 | data: string | ArrayBuffer;
201 | experimental_providerMetadata?: Record;
202 | mimeType: string;
203 | providerOptions?: Record;
204 | type: "file";
205 | }
206 | >;
207 | experimental_providerMetadata?: Record;
208 | providerOptions?: Record;
209 | role: "user";
210 | }
211 | | {
212 | content:
213 | | string
214 | | Array<
215 | | {
216 | experimental_providerMetadata?: Record;
217 | providerOptions?: Record;
218 | text: string;
219 | type: "text";
220 | }
221 | | {
222 | data: string | ArrayBuffer;
223 | experimental_providerMetadata?: Record;
224 | mimeType: string;
225 | providerOptions?: Record;
226 | type: "file";
227 | }
228 | | {
229 | experimental_providerMetadata?: Record;
230 | providerOptions?: Record;
231 | text: string;
232 | type: "reasoning";
233 | }
234 | | {
235 | data: string;
236 | experimental_providerMetadata?: Record;
237 | providerOptions?: Record;
238 | type: "redacted-reasoning";
239 | }
240 | | {
241 | args: any;
242 | experimental_providerMetadata?: Record;
243 | providerOptions?: Record;
244 | toolCallId: string;
245 | toolName: string;
246 | type: "tool-call";
247 | }
248 | >;
249 | experimental_providerMetadata?: Record;
250 | providerOptions?: Record;
251 | role: "assistant";
252 | }
253 | | {
254 | content: Array<{
255 | args?: any;
256 | experimental_content?: Array<
257 | | { text: string; type: "text" }
258 | | { data: string; mimeType?: string; type: "image" }
259 | >;
260 | experimental_providerMetadata?: Record;
261 | isError?: boolean;
262 | providerOptions?: Record;
263 | result: any;
264 | toolCallId: string;
265 | toolName: string;
266 | type: "tool-result";
267 | }>;
268 | experimental_providerMetadata?: Record;
269 | providerOptions?: Record;
270 | role: "tool";
271 | }
272 | | {
273 | content: string;
274 | experimental_providerMetadata?: Record;
275 | providerOptions?: Record;
276 | role: "system";
277 | };
278 | model?: string;
279 | order: number;
280 | parentMessageId?: string;
281 | status: "pending" | "success" | "failed";
282 | stepId?: string;
283 | stepOrder: number;
284 | text?: string;
285 | threadId: string;
286 | tool: boolean;
287 | userId?: string;
288 | }>;
289 | pending?: {
290 | _creationTime: number;
291 | _id: string;
292 | agentName?: string;
293 | embeddingId?:
294 | | string
295 | | string
296 | | string
297 | | string
298 | | string
299 | | string
300 | | string
301 | | string
302 | | string;
303 | error?: string;
304 | fileId?: string;
305 | id?: string;
306 | message?:
307 | | {
308 | content:
309 | | string
310 | | Array<
311 | | {
312 | experimental_providerMetadata?: Record;
313 | providerOptions?: Record;
314 | text: string;
315 | type: "text";
316 | }
317 | | {
318 | experimental_providerMetadata?: Record;
319 | image: string | ArrayBuffer;
320 | mimeType?: string;
321 | providerOptions?: Record;
322 | type: "image";
323 | }
324 | | {
325 | data: string | ArrayBuffer;
326 | experimental_providerMetadata?: Record;
327 | mimeType: string;
328 | providerOptions?: Record;
329 | type: "file";
330 | }
331 | >;
332 | experimental_providerMetadata?: Record;
333 | providerOptions?: Record;
334 | role: "user";
335 | }
336 | | {
337 | content:
338 | | string
339 | | Array<
340 | | {
341 | experimental_providerMetadata?: Record;
342 | providerOptions?: Record;
343 | text: string;
344 | type: "text";
345 | }
346 | | {
347 | data: string | ArrayBuffer;
348 | experimental_providerMetadata?: Record;
349 | mimeType: string;
350 | providerOptions?: Record;
351 | type: "file";
352 | }
353 | | {
354 | experimental_providerMetadata?: Record;
355 | providerOptions?: Record;
356 | text: string;
357 | type: "reasoning";
358 | }
359 | | {
360 | data: string;
361 | experimental_providerMetadata?: Record;
362 | providerOptions?: Record;
363 | type: "redacted-reasoning";
364 | }
365 | | {
366 | args: any;
367 | experimental_providerMetadata?: Record;
368 | providerOptions?: Record;
369 | toolCallId: string;
370 | toolName: string;
371 | type: "tool-call";
372 | }
373 | >;
374 | experimental_providerMetadata?: Record;
375 | providerOptions?: Record;
376 | role: "assistant";
377 | }
378 | | {
379 | content: Array<{
380 | args?: any;
381 | experimental_content?: Array<
382 | | { text: string; type: "text" }
383 | | { data: string; mimeType?: string; type: "image" }
384 | >;
385 | experimental_providerMetadata?: Record;
386 | isError?: boolean;
387 | providerOptions?: Record;
388 | result: any;
389 | toolCallId: string;
390 | toolName: string;
391 | type: "tool-result";
392 | }>;
393 | experimental_providerMetadata?: Record;
394 | providerOptions?: Record;
395 | role: "tool";
396 | }
397 | | {
398 | content: string;
399 | experimental_providerMetadata?: Record;
400 | providerOptions?: Record;
401 | role: "system";
402 | };
403 | model?: string;
404 | order: number;
405 | parentMessageId?: string;
406 | status: "pending" | "success" | "failed";
407 | stepId?: string;
408 | stepOrder: number;
409 | text?: string;
410 | threadId: string;
411 | tool: boolean;
412 | userId?: string;
413 | };
414 | }
415 | >;
416 | addStep: FunctionReference<
417 | "mutation",
418 | "internal",
419 | {
420 | embeddings?: {
421 | dimension: 128 | 256 | 512 | 768 | 1024 | 1536 | 2048 | 3072 | 4096;
422 | model: string;
423 | vectors: Array | null>;
424 | };
425 | failPendingSteps?: boolean;
426 | messageId: string;
427 | step: {
428 | messages: Array<{
429 | fileId?: string;
430 | id?: string;
431 | message:
432 | | {
433 | content:
434 | | string
435 | | Array<
436 | | {
437 | experimental_providerMetadata?: Record<
438 | string,
439 | any
440 | >;
441 | providerOptions?: Record;
442 | text: string;
443 | type: "text";
444 | }
445 | | {
446 | experimental_providerMetadata?: Record<
447 | string,
448 | any
449 | >;
450 | image: string | ArrayBuffer;
451 | mimeType?: string;
452 | providerOptions?: Record;
453 | type: "image";
454 | }
455 | | {
456 | data: string | ArrayBuffer;
457 | experimental_providerMetadata?: Record<
458 | string,
459 | any
460 | >;
461 | mimeType: string;
462 | providerOptions?: Record;
463 | type: "file";
464 | }
465 | >;
466 | experimental_providerMetadata?: Record;
467 | providerOptions?: Record;
468 | role: "user";
469 | }
470 | | {
471 | content:
472 | | string
473 | | Array<
474 | | {
475 | experimental_providerMetadata?: Record<
476 | string,
477 | any
478 | >;
479 | providerOptions?: Record;
480 | text: string;
481 | type: "text";
482 | }
483 | | {
484 | data: string | ArrayBuffer;
485 | experimental_providerMetadata?: Record<
486 | string,
487 | any
488 | >;
489 | mimeType: string;
490 | providerOptions?: Record;
491 | type: "file";
492 | }
493 | | {
494 | experimental_providerMetadata?: Record<
495 | string,
496 | any
497 | >;
498 | providerOptions?: Record;
499 | text: string;
500 | type: "reasoning";
501 | }
502 | | {
503 | data: string;
504 | experimental_providerMetadata?: Record<
505 | string,
506 | any
507 | >;
508 | providerOptions?: Record;
509 | type: "redacted-reasoning";
510 | }
511 | | {
512 | args: any;
513 | experimental_providerMetadata?: Record<
514 | string,
515 | any
516 | >;
517 | providerOptions?: Record;
518 | toolCallId: string;
519 | toolName: string;
520 | type: "tool-call";
521 | }
522 | >;
523 | experimental_providerMetadata?: Record;
524 | providerOptions?: Record;
525 | role: "assistant";
526 | }
527 | | {
528 | content: Array<{
529 | args?: any;
530 | experimental_content?: Array<
531 | | { text: string; type: "text" }
532 | | { data: string; mimeType?: string; type: "image" }
533 | >;
534 | experimental_providerMetadata?: Record;
535 | isError?: boolean;
536 | providerOptions?: Record;
537 | result: any;
538 | toolCallId: string;
539 | toolName: string;
540 | type: "tool-result";
541 | }>;
542 | experimental_providerMetadata?: Record;
543 | providerOptions?: Record;
544 | role: "tool";
545 | }
546 | | {
547 | content: string;
548 | experimental_providerMetadata?: Record;
549 | providerOptions?: Record;
550 | role: "system";
551 | };
552 | }>;
553 | step: {
554 | experimental_providerMetadata?: Record;
555 | files?: Array;
556 | finishReason:
557 | | "stop"
558 | | "length"
559 | | "content-filter"
560 | | "tool-calls"
561 | | "error"
562 | | "other"
563 | | "unknown";
564 | isContinued: boolean;
565 | logprobs?: any;
566 | providerMetadata?: Record;
567 | providerOptions?: Record;
568 | reasoning?: string;
569 | reasoningDetails?: Array;
570 | request?: {
571 | body?: any;
572 | headers?: Record;
573 | method?: string;
574 | url?: string;
575 | };
576 | response?: {
577 | body?: any;
578 | headers?: Record;
579 | id: string;
580 | messages: Array<{
581 | fileId?: string;
582 | id?: string;
583 | message:
584 | | {
585 | content:
586 | | string
587 | | Array<
588 | | {
589 | experimental_providerMetadata?: Record<
590 | string,
591 | any
592 | >;
593 | providerOptions?: Record;
594 | text: string;
595 | type: "text";
596 | }
597 | | {
598 | experimental_providerMetadata?: Record<
599 | string,
600 | any
601 | >;
602 | image: string | ArrayBuffer;
603 | mimeType?: string;
604 | providerOptions?: Record;
605 | type: "image";
606 | }
607 | | {
608 | data: string | ArrayBuffer;
609 | experimental_providerMetadata?: Record<
610 | string,
611 | any
612 | >;
613 | mimeType: string;
614 | providerOptions?: Record;
615 | type: "file";
616 | }
617 | >;
618 | experimental_providerMetadata?: Record;
619 | providerOptions?: Record;
620 | role: "user";
621 | }
622 | | {
623 | content:
624 | | string
625 | | Array<
626 | | {
627 | experimental_providerMetadata?: Record<
628 | string,
629 | any
630 | >;
631 | providerOptions?: Record;
632 | text: string;
633 | type: "text";
634 | }
635 | | {
636 | data: string | ArrayBuffer;
637 | experimental_providerMetadata?: Record<
638 | string,
639 | any
640 | >;
641 | mimeType: string;
642 | providerOptions?: Record;
643 | type: "file";
644 | }
645 | | {
646 | experimental_providerMetadata?: Record<
647 | string,
648 | any
649 | >;
650 | providerOptions?: Record;
651 | text: string;
652 | type: "reasoning";
653 | }
654 | | {
655 | data: string;
656 | experimental_providerMetadata?: Record<
657 | string,
658 | any
659 | >;
660 | providerOptions?: Record;
661 | type: "redacted-reasoning";
662 | }
663 | | {
664 | args: any;
665 | experimental_providerMetadata?: Record<
666 | string,
667 | any
668 | >;
669 | providerOptions?: Record;
670 | toolCallId: string;
671 | toolName: string;
672 | type: "tool-call";
673 | }
674 | >;
675 | experimental_providerMetadata?: Record;
676 | providerOptions?: Record;
677 | role: "assistant";
678 | }
679 | | {
680 | content: Array<{
681 | args?: any;
682 | experimental_content?: Array<
683 | | { text: string; type: "text" }
684 | | { data: string; mimeType?: string; type: "image" }
685 | >;
686 | experimental_providerMetadata?: Record;
687 | isError?: boolean;
688 | providerOptions?: Record;
689 | result: any;
690 | toolCallId: string;
691 | toolName: string;
692 | type: "tool-result";
693 | }>;
694 | experimental_providerMetadata?: Record;
695 | providerOptions?: Record;
696 | role: "tool";
697 | }
698 | | {
699 | content: string;
700 | experimental_providerMetadata?: Record;
701 | providerOptions?: Record;
702 | role: "system";
703 | };
704 | }>;
705 | modelId: string;
706 | timestamp: number;
707 | };
708 | sources?: Array<{
709 | id: string;
710 | providerMetadata?: Record;
711 | sourceType: "url";
712 | title?: string;
713 | url: string;
714 | }>;
715 | stepType: "initial" | "continue" | "tool-result";
716 | text: string;
717 | toolCalls: Array<{
718 | args: any;
719 | experimental_providerMetadata?: Record;
720 | providerOptions?: Record;
721 | toolCallId: string;
722 | toolName: string;
723 | type: "tool-call";
724 | }>;
725 | toolResults: Array<{
726 | args?: any;
727 | experimental_content?: Array<
728 | | { text: string; type: "text" }
729 | | { data: string; mimeType?: string; type: "image" }
730 | >;
731 | experimental_providerMetadata?: Record;
732 | isError?: boolean;
733 | providerOptions?: Record;
734 | result: any;
735 | toolCallId: string;
736 | toolName: string;
737 | type: "tool-result";
738 | }>;
739 | usage?: {
740 | completionTokens: number;
741 | promptTokens: number;
742 | totalTokens: number;
743 | };
744 | warnings?: Array<
745 | | {
746 | details?: string;
747 | setting: string;
748 | type: "unsupported-setting";
749 | }
750 | | { details?: string; tool: any; type: "unsupported-tool" }
751 | | { message: string; type: "other" }
752 | >;
753 | };
754 | };
755 | threadId: string;
756 | },
757 | Array<{
758 | _creationTime: number;
759 | _id: string;
760 | order: number;
761 | parentMessageId: string;
762 | status: "pending" | "success" | "failed";
763 | step: {
764 | experimental_providerMetadata?: Record;
765 | files?: Array;
766 | finishReason:
767 | | "stop"
768 | | "length"
769 | | "content-filter"
770 | | "tool-calls"
771 | | "error"
772 | | "other"
773 | | "unknown";
774 | isContinued: boolean;
775 | logprobs?: any;
776 | providerMetadata?: Record;
777 | providerOptions?: Record;
778 | reasoning?: string;
779 | reasoningDetails?: Array;
780 | request?: {
781 | body?: any;
782 | headers?: Record;
783 | method?: string;
784 | url?: string;
785 | };
786 | response?: {
787 | body?: any;
788 | headers?: Record;
789 | id: string;
790 | messages: Array<{
791 | fileId?: string;
792 | id?: string;
793 | message:
794 | | {
795 | content:
796 | | string
797 | | Array<
798 | | {
799 | experimental_providerMetadata?: Record<
800 | string,
801 | any
802 | >;
803 | providerOptions?: Record;
804 | text: string;
805 | type: "text";
806 | }
807 | | {
808 | experimental_providerMetadata?: Record<
809 | string,
810 | any
811 | >;
812 | image: string | ArrayBuffer;
813 | mimeType?: string;
814 | providerOptions?: Record;
815 | type: "image";
816 | }
817 | | {
818 | data: string | ArrayBuffer;
819 | experimental_providerMetadata?: Record<
820 | string,
821 | any
822 | >;
823 | mimeType: string;
824 | providerOptions?: Record;
825 | type: "file";
826 | }
827 | >;
828 | experimental_providerMetadata?: Record;
829 | providerOptions?: Record;
830 | role: "user";
831 | }
832 | | {
833 | content:
834 | | string
835 | | Array<
836 | | {
837 | experimental_providerMetadata?: Record<
838 | string,
839 | any
840 | >;
841 | providerOptions?: Record;
842 | text: string;
843 | type: "text";
844 | }
845 | | {
846 | data: string | ArrayBuffer;
847 | experimental_providerMetadata?: Record<
848 | string,
849 | any
850 | >;
851 | mimeType: string;
852 | providerOptions?: Record;
853 | type: "file";
854 | }
855 | | {
856 | experimental_providerMetadata?: Record<
857 | string,
858 | any
859 | >;
860 | providerOptions?: Record;
861 | text: string;
862 | type: "reasoning";
863 | }
864 | | {
865 | data: string;
866 | experimental_providerMetadata?: Record<
867 | string,
868 | any
869 | >;
870 | providerOptions?: Record;
871 | type: "redacted-reasoning";
872 | }
873 | | {
874 | args: any;
875 | experimental_providerMetadata?: Record<
876 | string,
877 | any
878 | >;
879 | providerOptions?: Record;
880 | toolCallId: string;
881 | toolName: string;
882 | type: "tool-call";
883 | }
884 | >;
885 | experimental_providerMetadata?: Record;
886 | providerOptions?: Record;
887 | role: "assistant";
888 | }
889 | | {
890 | content: Array<{
891 | args?: any;
892 | experimental_content?: Array<
893 | | { text: string; type: "text" }
894 | | { data: string; mimeType?: string; type: "image" }
895 | >;
896 | experimental_providerMetadata?: Record;
897 | isError?: boolean;
898 | providerOptions?: Record;
899 | result: any;
900 | toolCallId: string;
901 | toolName: string;
902 | type: "tool-result";
903 | }>;
904 | experimental_providerMetadata?: Record;
905 | providerOptions?: Record;
906 | role: "tool";
907 | }
908 | | {
909 | content: string;
910 | experimental_providerMetadata?: Record;
911 | providerOptions?: Record;
912 | role: "system";
913 | };
914 | }>;
915 | modelId: string;
916 | timestamp: number;
917 | };
918 | sources?: Array<{
919 | id: string;
920 | providerMetadata?: Record;
921 | sourceType: "url";
922 | title?: string;
923 | url: string;
924 | }>;
925 | stepType: "initial" | "continue" | "tool-result";
926 | text: string;
927 | toolCalls: Array<{
928 | args: any;
929 | experimental_providerMetadata?: Record;
930 | providerOptions?: Record;
931 | toolCallId: string;
932 | toolName: string;
933 | type: "tool-call";
934 | }>;
935 | toolResults: Array<{
936 | args?: any;
937 | experimental_content?: Array<
938 | | { text: string; type: "text" }
939 | | { data: string; mimeType?: string; type: "image" }
940 | >;
941 | experimental_providerMetadata?: Record;
942 | isError?: boolean;
943 | providerOptions?: Record;
944 | result: any;
945 | toolCallId: string;
946 | toolName: string;
947 | type: "tool-result";
948 | }>;
949 | usage?: {
950 | completionTokens: number;
951 | promptTokens: number;
952 | totalTokens: number;
953 | };
954 | warnings?: Array<
955 | | {
956 | details?: string;
957 | setting: string;
958 | type: "unsupported-setting";
959 | }
960 | | { details?: string; tool: any; type: "unsupported-tool" }
961 | | { message: string; type: "other" }
962 | >;
963 | };
964 | stepOrder: number;
965 | threadId: string;
966 | }>
967 | >;
968 | commitMessage: FunctionReference<
969 | "mutation",
970 | "internal",
971 | { messageId: string },
972 | null
973 | >;
974 | createThread: FunctionReference<
975 | "mutation",
976 | "internal",
977 | {
978 | defaultSystemPrompt?: string;
979 | parentThreadIds?: Array;
980 | summary?: string;
981 | title?: string;
982 | userId?: string;
983 | },
984 | {
985 | _creationTime: number;
986 | _id: string;
987 | defaultSystemPrompt?: string;
988 | order?: number;
989 | parentThreadIds?: Array;
990 | status: "active" | "archived";
991 | summary?: string;
992 | title?: string;
993 | userId?: string;
994 | }
995 | >;
996 | deleteAllForThreadIdAsync: FunctionReference<
997 | "mutation",
998 | "internal",
999 | { cursor?: string; limit?: number; threadId: string },
1000 | { cursor: string; isDone: boolean }
1001 | >;
1002 | deleteAllForThreadIdSync: FunctionReference<
1003 | "action",
1004 | "internal",
1005 | { cursor?: string; limit?: number; threadId: string },
1006 | { cursor: string; isDone: boolean }
1007 | >;
1008 | deleteAllForUserId: FunctionReference<
1009 | "action",
1010 | "internal",
1011 | { userId: string },
1012 | null
1013 | >;
1014 | deleteAllForUserIdAsync: FunctionReference<
1015 | "mutation",
1016 | "internal",
1017 | { userId: string },
1018 | boolean
1019 | >;
1020 | getFilesToDelete: FunctionReference<
1021 | "query",
1022 | "internal",
1023 | { cursor?: string; limit?: number },
1024 | {
1025 | continueCursor: string;
1026 | files: Array<{
1027 | _creationTime: number;
1028 | _id: string;
1029 | hash: string;
1030 | refcount: number;
1031 | storageId: string;
1032 | }>;
1033 | isDone: boolean;
1034 | }
1035 | >;
1036 | getThread: FunctionReference<
1037 | "query",
1038 | "internal",
1039 | { threadId: string },
1040 | {
1041 | _creationTime: number;
1042 | _id: string;
1043 | defaultSystemPrompt?: string;
1044 | order?: number;
1045 | parentThreadIds?: Array;
1046 | status: "active" | "archived";
1047 | summary?: string;
1048 | title?: string;
1049 | userId?: string;
1050 | } | null
1051 | >;
1052 | getThreadMessages: FunctionReference<
1053 | "query",
1054 | "internal",
1055 | {
1056 | isTool?: boolean;
1057 | order?: "asc" | "desc";
1058 | paginationOpts?: {
1059 | cursor: string | null;
1060 | endCursor?: string | null;
1061 | id?: number;
1062 | maximumBytesRead?: number;
1063 | maximumRowsRead?: number;
1064 | numItems: number;
1065 | };
1066 | parentMessageId?: string;
1067 | statuses?: Array<"pending" | "success" | "failed">;
1068 | threadId: string;
1069 | },
1070 | {
1071 | continueCursor: string;
1072 | isDone: boolean;
1073 | page: Array<{
1074 | _creationTime: number;
1075 | _id: string;
1076 | agentName?: string;
1077 | embeddingId?:
1078 | | string
1079 | | string
1080 | | string
1081 | | string
1082 | | string
1083 | | string
1084 | | string
1085 | | string
1086 | | string;
1087 | error?: string;
1088 | fileId?: string;
1089 | id?: string;
1090 | message?:
1091 | | {
1092 | content:
1093 | | string
1094 | | Array<
1095 | | {
1096 | experimental_providerMetadata?: Record;
1097 | providerOptions?: Record;
1098 | text: string;
1099 | type: "text";
1100 | }
1101 | | {
1102 | experimental_providerMetadata?: Record;
1103 | image: string | ArrayBuffer;
1104 | mimeType?: string;
1105 | providerOptions?: Record;
1106 | type: "image";
1107 | }
1108 | | {
1109 | data: string | ArrayBuffer;
1110 | experimental_providerMetadata?: Record;
1111 | mimeType: string;
1112 | providerOptions?: Record;
1113 | type: "file";
1114 | }
1115 | >;
1116 | experimental_providerMetadata?: Record;
1117 | providerOptions?: Record;
1118 | role: "user";
1119 | }
1120 | | {
1121 | content:
1122 | | string
1123 | | Array<
1124 | | {
1125 | experimental_providerMetadata?: Record;
1126 | providerOptions?: Record;
1127 | text: string;
1128 | type: "text";
1129 | }
1130 | | {
1131 | data: string | ArrayBuffer;
1132 | experimental_providerMetadata?: Record;
1133 | mimeType: string;
1134 | providerOptions?: Record;
1135 | type: "file";
1136 | }
1137 | | {
1138 | experimental_providerMetadata?: Record;
1139 | providerOptions?: Record;
1140 | text: string;
1141 | type: "reasoning";
1142 | }
1143 | | {
1144 | data: string;
1145 | experimental_providerMetadata?: Record;
1146 | providerOptions?: Record;
1147 | type: "redacted-reasoning";
1148 | }
1149 | | {
1150 | args: any;
1151 | experimental_providerMetadata?: Record;
1152 | providerOptions?: Record;
1153 | toolCallId: string;
1154 | toolName: string;
1155 | type: "tool-call";
1156 | }
1157 | >;
1158 | experimental_providerMetadata?: Record;
1159 | providerOptions?: Record;
1160 | role: "assistant";
1161 | }
1162 | | {
1163 | content: Array<{
1164 | args?: any;
1165 | experimental_content?: Array<
1166 | | { text: string; type: "text" }
1167 | | { data: string; mimeType?: string; type: "image" }
1168 | >;
1169 | experimental_providerMetadata?: Record;
1170 | isError?: boolean;
1171 | providerOptions?: Record;
1172 | result: any;
1173 | toolCallId: string;
1174 | toolName: string;
1175 | type: "tool-result";
1176 | }>;
1177 | experimental_providerMetadata?: Record;
1178 | providerOptions?: Record;
1179 | role: "tool";
1180 | }
1181 | | {
1182 | content: string;
1183 | experimental_providerMetadata?: Record;
1184 | providerOptions?: Record;
1185 | role: "system";
1186 | };
1187 | model?: string;
1188 | order: number;
1189 | parentMessageId?: string;
1190 | status: "pending" | "success" | "failed";
1191 | stepId?: string;
1192 | stepOrder: number;
1193 | text?: string;
1194 | threadId: string;
1195 | tool: boolean;
1196 | userId?: string;
1197 | }>;
1198 | pageStatus?: "SplitRecommended" | "SplitRequired" | null;
1199 | splitCursor?: string | null;
1200 | }
1201 | >;
1202 | getThreadsByUserId: FunctionReference<
1203 | "query",
1204 | "internal",
1205 | {
1206 | order?: "asc" | "desc";
1207 | paginationOpts?: {
1208 | cursor: string | null;
1209 | endCursor?: string | null;
1210 | id?: number;
1211 | maximumBytesRead?: number;
1212 | maximumRowsRead?: number;
1213 | numItems: number;
1214 | };
1215 | userId: string;
1216 | },
1217 | {
1218 | continueCursor: string;
1219 | isDone: boolean;
1220 | page: Array<{
1221 | _creationTime: number;
1222 | _id: string;
1223 | defaultSystemPrompt?: string;
1224 | order?: number;
1225 | parentThreadIds?: Array;
1226 | status: "active" | "archived";
1227 | summary?: string;
1228 | title?: string;
1229 | userId?: string;
1230 | }>;
1231 | pageStatus?: "SplitRecommended" | "SplitRequired" | null;
1232 | splitCursor?: string | null;
1233 | }
1234 | >;
1235 | rollbackMessage: FunctionReference<
1236 | "mutation",
1237 | "internal",
1238 | { error?: string; messageId: string },
1239 | null
1240 | >;
1241 | searchMessages: FunctionReference<
1242 | "action",
1243 | "internal",
1244 | {
1245 | limit: number;
1246 | messageRange?: { after: number; before: number };
1247 | parentMessageId?: string;
1248 | text?: string;
1249 | threadId?: string;
1250 | userId?: string;
1251 | vector?: Array;
1252 | vectorModel?: string;
1253 | vectorScoreThreshold?: number;
1254 | },
1255 | Array<{
1256 | _creationTime: number;
1257 | _id: string;
1258 | agentName?: string;
1259 | embeddingId?:
1260 | | string
1261 | | string
1262 | | string
1263 | | string
1264 | | string
1265 | | string
1266 | | string
1267 | | string
1268 | | string;
1269 | error?: string;
1270 | fileId?: string;
1271 | id?: string;
1272 | message?:
1273 | | {
1274 | content:
1275 | | string
1276 | | Array<
1277 | | {
1278 | experimental_providerMetadata?: Record;
1279 | providerOptions?: Record;
1280 | text: string;
1281 | type: "text";
1282 | }
1283 | | {
1284 | experimental_providerMetadata?: Record;
1285 | image: string | ArrayBuffer;
1286 | mimeType?: string;
1287 | providerOptions?: Record;
1288 | type: "image";
1289 | }
1290 | | {
1291 | data: string | ArrayBuffer;
1292 | experimental_providerMetadata?: Record;
1293 | mimeType: string;
1294 | providerOptions?: Record;
1295 | type: "file";
1296 | }
1297 | >;
1298 | experimental_providerMetadata?: Record;
1299 | providerOptions?: Record;
1300 | role: "user";
1301 | }
1302 | | {
1303 | content:
1304 | | string
1305 | | Array<
1306 | | {
1307 | experimental_providerMetadata?: Record;
1308 | providerOptions?: Record;
1309 | text: string;
1310 | type: "text";
1311 | }
1312 | | {
1313 | data: string | ArrayBuffer;
1314 | experimental_providerMetadata?: Record;
1315 | mimeType: string;
1316 | providerOptions?: Record