From cd0fb697620d1a0d6ca1619b72cfc6756c27df61 Mon Sep 17 00:00:00 2001 From: Divyanshgupta030 <145568562+Divyanshgupta030@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:54:10 +0530 Subject: [PATCH 1/8] add onbaording ai agent --- apps/api/package.json | 4 +- apps/app/actions/action.tsx | 50 ++++ .../app/(onboarding)/onboarding/layout.tsx | 5 + apps/app/app/(onboarding)/onboarding/page.tsx | 6 + apps/app/app/(routes)/layout.tsx | 20 ++ apps/app/app/layout.tsx | 45 ++-- .../components/custom/onboarding/chatList.tsx | 15 ++ .../components/custom/onboarding/chatbox.tsx | 34 +++ .../components/custom/onboarding/message.tsx | 57 +++++ apps/app/lib/ai.tsx | 19 ++ apps/app/middleware.ts | 14 +- apps/app/package.json | 3 + packages/auth/src/auth.ts | 11 + .../20241128154235_onboarding/migration.sql | 2 + packages/database/prisma/schema.prisma | 1 + pnpm-lock.yaml | 234 ++++++++++++++++++ 16 files changed, 487 insertions(+), 33 deletions(-) create mode 100644 apps/app/actions/action.tsx create mode 100644 apps/app/app/(onboarding)/onboarding/layout.tsx create mode 100644 apps/app/app/(onboarding)/onboarding/page.tsx create mode 100644 apps/app/app/(routes)/layout.tsx create mode 100644 apps/app/components/custom/onboarding/chatList.tsx create mode 100644 apps/app/components/custom/onboarding/chatbox.tsx create mode 100644 apps/app/components/custom/onboarding/message.tsx create mode 100644 apps/app/lib/ai.tsx create mode 100644 packages/database/prisma/migrations/20241128154235_onboarding/migration.sql diff --git a/apps/api/package.json b/apps/api/package.json index 082196a..470768f 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -4,8 +4,8 @@ "dev": "next dev -p 3001 --turbopack", "prebuild": "turbo --filter=@repo/db db:generate", "build": "next build", - "start": "next start -p 5555", - "preview": "next build && next start -p 5555", + "start": "next start -p 3001", + "preview": "next build && next start -p 3001", "lint": "next lint", "lint:fix": "next lint --fix", "test": "vitest --run", diff --git a/apps/app/actions/action.tsx b/apps/app/actions/action.tsx new file mode 100644 index 0000000..ccaace6 --- /dev/null +++ b/apps/app/actions/action.tsx @@ -0,0 +1,50 @@ +"use server"; +import { ReactNode } from "react"; +import { getMutableAIState, streamUI } from "ai/rsc"; +import { togetherai } from "@ai-sdk/togetherai"; +import { AI } from "@/lib/ai"; +import { BotMessage } from "@/components/custom/onboarding/message"; + +export type ServerMessage = { + role: "user" | "assistant"; + content: string; +}; + +export type ClientMessage = { + id: number; + role: "user" | "assistant"; + display: ReactNode; +}; + +export const sendMessage = async ( + message: string +): Promise<{ + id: number; + role: "user" | "assistant"; + display: ReactNode; +}> => { + const history = getMutableAIState(); + history.update([...history.get(), { role: "user", content: message }]); + const response = await streamUI({ + model: togetherai("deepseek-ai/deepseek-llm-67b-chat"), + system: "You are a helpful assistant. give answer in only 10 words", + messages: [{ role: "user", content: message }, ...history.get()], + text: ({ content, done }) => { + if (done) { + history.update([ + ...history.get(), + { role: "assistant", content: content }, + ]); + console.log("done", ...history.get()); + } + return {content}; + }, + }); + + + return { + id: Date.now(), + role: "assistant" as const, + display: response.value, + }; +}; diff --git a/apps/app/app/(onboarding)/onboarding/layout.tsx b/apps/app/app/(onboarding)/onboarding/layout.tsx new file mode 100644 index 0000000..d1eacb5 --- /dev/null +++ b/apps/app/app/(onboarding)/onboarding/layout.tsx @@ -0,0 +1,5 @@ +import { AI } from "@/lib/ai"; + +export default function OnboardingLayout({children}: {children: React.ReactNode}) { + return {children}; +} \ No newline at end of file diff --git a/apps/app/app/(onboarding)/onboarding/page.tsx b/apps/app/app/(onboarding)/onboarding/page.tsx new file mode 100644 index 0000000..8805568 --- /dev/null +++ b/apps/app/app/(onboarding)/onboarding/page.tsx @@ -0,0 +1,6 @@ +import Chatbox from "@/components/custom/onboarding/chatbox"; + +export default function Onboarding() { + + return +} \ No newline at end of file diff --git a/apps/app/app/(routes)/layout.tsx b/apps/app/app/(routes)/layout.tsx new file mode 100644 index 0000000..e3fdd33 --- /dev/null +++ b/apps/app/app/(routes)/layout.tsx @@ -0,0 +1,20 @@ +import Infobar from "@/components/custom/infobar/infobar"; +import ProgressBar from "@/components/custom/progress.bar"; +import { AppSidebar } from "@/components/custom/sidebar/sidebar"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { cookies } from "next/headers"; +async function RouteLayout({children}: {children: React.ReactNode}) { + const cookieStore = await cookies(); + const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true"; + return ( + + +
+ + + {children} +
+
+ ); +} +export default RouteLayout; diff --git a/apps/app/app/layout.tsx b/apps/app/app/layout.tsx index c1d8ddb..5950190 100644 --- a/apps/app/app/layout.tsx +++ b/apps/app/app/layout.tsx @@ -2,13 +2,7 @@ import type { Metadata } from "next"; import { GeistSans } from "geist/font/sans"; import "./globals.css"; import { ThemeProvider } from "@/hooks/theme-provider"; -import { SidebarProvider } from "@/components/ui/sidebar"; -import { AppSidebar } from "@/components/custom/sidebar/sidebar"; -import { cookies } from "next/headers"; -import Infobar from "@/components/custom/infobar/infobar"; -import ProgressBar from "@/components/custom/progress.bar"; import { PosthogProvider } from "@/hooks/posthog"; - export const metadata: Metadata = { title: "Plura", description: "Generated by create next app", @@ -19,33 +13,24 @@ async function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const cookieStore = await cookies(); - const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true"; return ( - - - - + + - - -
- - - {children} -
-
-
- -
- + + {children} + + + + ); } diff --git a/apps/app/components/custom/onboarding/chatList.tsx b/apps/app/components/custom/onboarding/chatList.tsx new file mode 100644 index 0000000..489451b --- /dev/null +++ b/apps/app/components/custom/onboarding/chatList.tsx @@ -0,0 +1,15 @@ +import type { UIState } from "@/lib/ai"; + +export function ChatList({ messages }: { messages: UIState[number][] }) { + if (!messages.length) return null; + + return ( +
+ {messages.map((message, index) => ( +
+ {message.display} +
+ ))} +
+ ); +} diff --git a/apps/app/components/custom/onboarding/chatbox.tsx b/apps/app/components/custom/onboarding/chatbox.tsx new file mode 100644 index 0000000..a4312aa --- /dev/null +++ b/apps/app/components/custom/onboarding/chatbox.tsx @@ -0,0 +1,34 @@ +"use client" + +import { Input } from "@/components/ui/input"; +import { AI } from "@/lib/ai"; +import { useActions, useUIState } from "ai/rsc"; +import { useState } from "react"; +import { ChatList } from "./chatList"; +import { UserMessage } from "./message"; + +export default function Chatbox() { + const [messages, setMessages] = useUIState(); + const [value, setValue] = useState(""); + const { sendMessage } = useActions(); + const handleSubmit = async() => { + setMessages((currentMessages) => [ + ...currentMessages, + { + id: Date.now(), + role: "user", + display: {value}, + }, + ]); + const response = await sendMessage(value); + setMessages((currentMessages) => [...currentMessages, response]); + } + + return ( +
+ + setValue(e.target.value)} /> + +
+ ); +} \ No newline at end of file diff --git a/apps/app/components/custom/onboarding/message.tsx b/apps/app/components/custom/onboarding/message.tsx new file mode 100644 index 0000000..2a497e4 --- /dev/null +++ b/apps/app/components/custom/onboarding/message.tsx @@ -0,0 +1,57 @@ +import { cn } from "@/lib/utils"; +import { Sparkle, UserIcon } from "lucide-react"; + +// Different types of message bubbles. +export function UserMessage({ children }: { children: React.ReactNode }) { + return ( +
+
+ +
+
+ {children} +
+
+ ); +} + +export function BotMessage({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+
+ +
+
+ {children} +
+
+ ); +} + +export function BotCard({ + children, + showAvatar = true, +}: { + children: React.ReactNode; + showAvatar?: boolean; +}) { + return ( +
+
+ +
+
{children}
+
+ ); +} diff --git a/apps/app/lib/ai.tsx b/apps/app/lib/ai.tsx new file mode 100644 index 0000000..1172ed2 --- /dev/null +++ b/apps/app/lib/ai.tsx @@ -0,0 +1,19 @@ +import { createAI } from "ai/rsc"; +import { ClientMessage, ServerMessage, sendMessage } from "@/actions/action"; + +export type AIState = ServerMessage[]; +export type UIState = ClientMessage[]; + +export const AI = createAI< + AIState, + UIState, + { + sendMessage: (message: string) => Promise; + } +>({ + initialAIState: [], + initialUIState: [], + actions: { + sendMessage, + }, +}); diff --git a/apps/app/middleware.ts b/apps/app/middleware.ts index 18724eb..723fadb 100644 --- a/apps/app/middleware.ts +++ b/apps/app/middleware.ts @@ -9,6 +9,8 @@ const redirectUrl = process.env.NODE_ENV === "production" ? "https://www.plura.pro/auth" : "http://localhost:3003/auth"; +const appDomain = process.env.NODE_ENV === "production" ? "https://app.plura.pro" : "http://localhost:3002"; + export default async function authMiddleware(request: NextRequest) { const { data: session } = await betterFetch( `${baseDomain}/api/auth/get-session`, @@ -24,9 +26,19 @@ export default async function authMiddleware(request: NextRequest) { console.log("redirecting to sign in"); return NextResponse.redirect(redirectUrl); } + const currentPath = request.nextUrl.pathname; + + + if (!currentPath.startsWith("/onboarding") && !session.user.isOnboarding) { + return NextResponse.redirect(`${appDomain}/onboarding`); + } + if (currentPath.startsWith("/onboarding") && session.user.isOnboarding) { + return NextResponse.redirect(`${appDomain}/home`); + } + return NextResponse.next(); } export const config = { - matcher: "/:path*", + matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'] }; diff --git a/apps/app/package.json b/apps/app/package.json index 3d98e59..eb8635e 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -14,6 +14,8 @@ "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache" }, "dependencies": { + "@ai-sdk/google": "^1.0.4", + "@ai-sdk/togetherai": "^0.0.3", "@better-fetch/fetch": "^1.1.12", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-alert-dialog": "^1.1.2", @@ -35,6 +37,7 @@ "@repo/db": "workspace:*", "@repo/types": "workspace:*", "@tabler/icons-react": "^3.21.0", + "ai": "^4.0.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "1.0.0", diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 4fc8cc0..385c1bb 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -18,6 +18,17 @@ export const auth = betterAuth({ }), secret: process.env.BETTER_AUTH_SECRET, plugins: [multiSession()], + user: { + additionalFields:{ + isOnboarding: { + type: "boolean", + nullable:false, + required:true, + input:false, + defaultValue: false + } + } + }, emailAndPassword: { enabled: true, autoSignIn: true, diff --git a/packages/database/prisma/migrations/20241128154235_onboarding/migration.sql b/packages/database/prisma/migrations/20241128154235_onboarding/migration.sql new file mode 100644 index 0000000..a965ece --- /dev/null +++ b/packages/database/prisma/migrations/20241128154235_onboarding/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "user" ADD COLUMN "isOnboarding" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index dbcf610..3a02015 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -17,6 +17,7 @@ model User { image String? createdAt DateTime updatedAt DateTime + isOnboarding Boolean @default(false) sessions Session[] accounts Account[] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9168f57..23176dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,6 +133,12 @@ importers: apps/app: dependencies: + '@ai-sdk/google': + specifier: ^1.0.4 + version: 1.0.4(zod@3.23.8) + '@ai-sdk/togetherai': + specifier: ^0.0.3 + version: 0.0.3(zod@3.23.8) '@better-fetch/fetch': specifier: ^1.1.12 version: 1.1.12 @@ -196,6 +202,9 @@ importers: '@tabler/icons-react': specifier: ^3.21.0 version: 3.22.0(react@19.0.0-rc-02c0e824-20241028) + ai: + specifier: ^4.0.8 + version: 4.0.8(react@19.0.0-rc-02c0e824-20241028)(zod@3.23.8) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -600,6 +609,71 @@ importers: packages: + '@ai-sdk/google@1.0.4': + resolution: {integrity: sha512-jUFhQjE7nfldU2umQjnDR/M8XlBupQquRkc2S7CwxcEEXVRg5vELYrLrD6xy387PKj7C8+Jg7sJCa3VjxCJ9Ig==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/openai-compatible@0.0.2': + resolution: {integrity: sha512-i79B7V8lA2HzwcDMj20zfIBIX4J2uwoF6Vy9lnWIW8kqY9yKBFujCRVCX2Qx7j24p4FQNHwsB7h30EftVprOmA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/provider-utils@2.0.0': + resolution: {integrity: sha512-uITgVJByhtzuQU2ZW+2CidWRmQqTUTp6KADevy+4aRnmILZxY2LCt+UZ/ZtjJqq0MffwkuQPPY21ExmFAQ6kKA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider-utils@2.0.2': + resolution: {integrity: sha512-IAvhKhdlXqiSmvx/D4uNlFYCl8dWT+M9K+IuEcSgnE2Aj27GWu8sDIpAf4r4Voc+wOUkOECVKQhFo8g9pozdjA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider@1.0.0': + resolution: {integrity: sha512-Sj29AzooJ7SYvhPd+AAWt/E7j63E9+AzRnoMHUaJPRYzOd/WDrVNxxv85prF9gDcQ7XPVlSk9j6oAZV9/DXYpA==} + engines: {node: '>=18'} + + '@ai-sdk/provider@1.0.1': + resolution: {integrity: sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.0.3': + resolution: {integrity: sha512-Mak7qIRlbgtP4I7EFoNKRIQTlABJHhgwrN8SV2WKKdmsfWK2RwcubQWz1hp88cQ0bpF6KxxjSY1UUnS/S9oR5g==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + + '@ai-sdk/togetherai@0.0.3': + resolution: {integrity: sha512-qFzQvW+SbIioMIHpMdVHQxl5n0soiwkz10lXyYFsUuBB/gNxC/LT7TXwcOc0rcVK+D9fO4uK//5NiXrLWpYH9w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/ui-utils@1.0.2': + resolution: {integrity: sha512-hHrUdeThGHu/rsGZBWQ9PjrAU9Htxgbo9MFyR5B/aWoNbBeXn1HLMY1+uMEnXL5pRPlmyVRjgIavWg7UgeNDOw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -3042,6 +3116,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -3355,6 +3432,18 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ai@4.0.8: + resolution: {integrity: sha512-pxoLsMY/sq2Y57sHohY+MGiF37fD/i/GWv1dONg5vchpoGBpSFtt3zFtZK2/vpjSjwXoC6YkId8ezAn9qkKTTQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -3919,6 +4008,9 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -4303,6 +4395,10 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventsource-parser@3.0.0: + resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} + engines: {node: '>=18.0.0'} + evt@2.5.8: resolution: {integrity: sha512-wjurRtEqepH03fZSzkCUAw2tNNtEoeoEYXf7EqG8uU52wQ3D/+ctyQE8hf+YR/5DXZlwl2jNGLMm4hAd/wyD6g==} @@ -4914,6 +5010,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -4926,6 +5025,11 @@ packages: engines: {node: '>=6'} hasBin: true + jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -5987,6 +6091,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -6256,6 +6363,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swr@2.2.5: + resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + synckit@0.9.2: resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -6297,6 +6409,10 @@ packages: peerDependencies: tslib: ^2 + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -6566,6 +6682,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6772,6 +6893,11 @@ packages: zod-error@1.5.0: resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==} + zod-to-json-schema@3.23.5: + resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==} + peerDependencies: + zod: ^3.23.3 + zod-validation-error@1.5.0: resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} engines: {node: '>=16.0.0'} @@ -6789,6 +6915,69 @@ packages: snapshots: + '@ai-sdk/google@1.0.4(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 1.0.1 + '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) + zod: 3.23.8 + + '@ai-sdk/openai-compatible@0.0.2(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 1.0.1 + '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) + zod: 3.23.8 + + '@ai-sdk/provider-utils@2.0.0(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 1.0.0 + eventsource-parser: 3.0.0 + nanoid: 5.0.8 + secure-json-parse: 2.7.0 + optionalDependencies: + zod: 3.23.8 + + '@ai-sdk/provider-utils@2.0.2(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 1.0.1 + eventsource-parser: 3.0.0 + nanoid: 3.3.7 + secure-json-parse: 2.7.0 + optionalDependencies: + zod: 3.23.8 + + '@ai-sdk/provider@1.0.0': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@1.0.1': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.0.3(react@19.0.0-rc-02c0e824-20241028)(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) + '@ai-sdk/ui-utils': 1.0.2(zod@3.23.8) + swr: 2.2.5(react@19.0.0-rc-02c0e824-20241028) + throttleit: 2.1.0 + optionalDependencies: + react: 19.0.0-rc-02c0e824-20241028 + zod: 3.23.8 + + '@ai-sdk/togetherai@0.0.3(zod@3.23.8)': + dependencies: + '@ai-sdk/openai-compatible': 0.0.2(zod@3.23.8) + '@ai-sdk/provider': 1.0.0 + '@ai-sdk/provider-utils': 2.0.0(zod@3.23.8) + zod: 3.23.8 + + '@ai-sdk/ui-utils@1.0.2(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 1.0.1 + '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) + zod-to-json-schema: 3.23.5(zod@3.23.8) + optionalDependencies: + zod: 3.23.8 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -9296,6 +9485,8 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/diff-match-patch@1.0.36': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.6 @@ -9772,6 +9963,19 @@ snapshots: acorn@8.14.0: {} + ai@4.0.8(react@19.0.0-rc-02c0e824-20241028)(zod@3.23.8): + dependencies: + '@ai-sdk/provider': 1.0.1 + '@ai-sdk/provider-utils': 2.0.2(zod@3.23.8) + '@ai-sdk/react': 1.0.3(react@19.0.0-rc-02c0e824-20241028)(zod@3.23.8) + '@ai-sdk/ui-utils': 1.0.2(zod@3.23.8) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + zod-to-json-schema: 3.23.5(zod@3.23.8) + optionalDependencies: + react: 19.0.0-rc-02c0e824-20241028 + zod: 3.23.8 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -10367,6 +10571,8 @@ snapshots: didyoumean@1.2.2: {} + diff-match-patch@1.0.5: {} + diff@4.0.2: optional: true @@ -11053,6 +11259,8 @@ snapshots: eventemitter3@4.0.7: {} + eventsource-parser@3.0.0: {} + evt@2.5.8: dependencies: minimal-polyfills: 2.2.3 @@ -11696,6 +11904,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: @@ -11704,6 +11914,12 @@ snapshots: json5@2.2.3: {} + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.3.0 + diff-match-patch: 1.0.5 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -13147,6 +13363,8 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + secure-json-parse@2.7.0: {} + selderee@0.11.0: dependencies: parseley: 0.12.1 @@ -13487,6 +13705,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swr@2.2.5(react@19.0.0-rc-02c0e824-20241028): + dependencies: + client-only: 0.0.1 + react: 19.0.0-rc-02c0e824-20241028 + use-sync-external-store: 1.2.2(react@19.0.0-rc-02c0e824-20241028) + synckit@0.9.2: dependencies: '@pkgr/core': 0.1.1 @@ -13546,6 +13770,8 @@ snapshots: dependencies: tslib: 2.8.1 + throttleit@2.1.0: {} + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -13796,6 +14022,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + use-sync-external-store@1.2.2(react@19.0.0-rc-02c0e824-20241028): + dependencies: + react: 19.0.0-rc-02c0e824-20241028 + util-deprecate@1.0.2: {} uuid@9.0.1: {} @@ -14019,6 +14249,10 @@ snapshots: dependencies: zod: 3.23.8 + zod-to-json-schema@3.23.5(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod-validation-error@1.5.0(zod@3.22.3): dependencies: zod: 3.22.3 From 0605d573971582b8566812acbfaa9a0a7d9fcd66 Mon Sep 17 00:00:00 2001 From: Divyanshgupta030 <145568562+Divyanshgupta030@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:14:00 +0530 Subject: [PATCH 2/8] add tools to ai --- apps/app/actions/action.tsx | 109 +++++++++++++-- apps/app/actions/session.ts | 17 +++ apps/app/app/layout.tsx | 31 ++--- .../custom/onboarding/BeatLoader.tsx | 27 ++++ .../custom/onboarding/ChatInput.tsx | 35 +++++ .../components/custom/onboarding/chatList.tsx | 5 +- .../components/custom/onboarding/chatbox.tsx | 125 ++++++++++++++++-- .../components/custom/onboarding/message.tsx | 48 ++----- .../components/custom/onboarding/proceed.tsx | 23 ++++ .../custom/onboarding/workspace-form.tsx | 32 +++++ apps/app/components/ui/textarea.tsx | 36 ++++- apps/app/hooks/use-scroll-to-bottom.ts | 29 ++++ apps/app/lib/ai.tsx | 4 +- apps/app/middleware.ts | 1 + apps/app/package.json | 5 +- apps/app/public/file.svg | 1 - apps/app/public/globe.svg | 1 - apps/app/public/images/plura.png | Bin 0 -> 23328 bytes apps/app/public/next.svg | 1 - apps/app/public/vercel.svg | 1 - apps/app/public/window.svg | 1 - apps/app/tailwind.config.ts | 14 ++ pnpm-lock.yaml | 80 ++++++++++- 23 files changed, 539 insertions(+), 87 deletions(-) create mode 100644 apps/app/actions/session.ts create mode 100644 apps/app/components/custom/onboarding/BeatLoader.tsx create mode 100644 apps/app/components/custom/onboarding/ChatInput.tsx create mode 100644 apps/app/components/custom/onboarding/proceed.tsx create mode 100644 apps/app/components/custom/onboarding/workspace-form.tsx create mode 100644 apps/app/hooks/use-scroll-to-bottom.ts delete mode 100644 apps/app/public/file.svg delete mode 100644 apps/app/public/globe.svg create mode 100644 apps/app/public/images/plura.png delete mode 100644 apps/app/public/next.svg delete mode 100644 apps/app/public/vercel.svg delete mode 100644 apps/app/public/window.svg diff --git a/apps/app/actions/action.tsx b/apps/app/actions/action.tsx index ccaace6..a3d4678 100644 --- a/apps/app/actions/action.tsx +++ b/apps/app/actions/action.tsx @@ -1,9 +1,15 @@ "use server"; import { ReactNode } from "react"; -import { getMutableAIState, streamUI } from "ai/rsc"; +import { createStreamableValue, getMutableAIState, streamUI } from "ai/rsc"; import { togetherai } from "@ai-sdk/togetherai"; import { AI } from "@/lib/ai"; import { BotMessage } from "@/components/custom/onboarding/message"; +import BeatLoader from "@/components/custom/onboarding/BeatLoader"; +import { getSession } from "./session"; +import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import Proceed from "@/components/custom/onboarding/proceed"; +import WorkspaceForm from "@/components/custom/onboarding/workspace-form"; export type ServerMessage = { role: "user" | "assistant"; @@ -24,23 +30,70 @@ export const sendMessage = async ( display: ReactNode; }> => { const history = getMutableAIState(); + console.log(history.get().length) + history.update([...history.get(), { role: "user", content: message }]); + const response = await streamUI({ - model: togetherai("deepseek-ai/deepseek-llm-67b-chat"), - system: "You are a helpful assistant. give answer in only 10 words", + model: togetherai("meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"), + system: `You are a helpful assistant. + if the message comes as "should we continue" then call proceed tool + if the message comes as "yes" then call workspace tool`, messages: [{ role: "user", content: message }, ...history.get()], - text: ({ content, done }) => { + temperature: 0, + initial: ( + + + + ), + text: async function ({ content, done }) { + await sleep(1000); if (done) { - history.update([ - ...history.get(), - { role: "assistant", content: content }, - ]); - console.log("done", ...history.get()); + history.done([...history.get(), { role: "assistant", content }]); + } return {content}; }, - }); + tools: { + workspace:{ + description: "yes", + parameters: z.object({}), + generate: async function* ({}) { + yield (console.log("call workspace"), + ( + + + + )); + return ( + + + + ); + }, + }, + proceed: { + description: "should we continue", + parameters: z.object({}), + generate: async function* ({}) { + yield (console.log("should we continue?"), + ( + + + + )); + + return ( + + + + ); + }, + }, + + }, + }); return { id: Date.now(), @@ -48,3 +101,39 @@ export const sendMessage = async ( display: response.value, }; }; + export const sendAiGreeting = async ():Promise => { + const session= await getSession() + const {name,email} = session!.user + const contentString = `Hi ${name}, welcome to Plura AI!.Your email is ${email}.I am going to help you with oboarding your acccount` + const history = getMutableAIState(); + console.log(history.get()) + const value = await streamUI({ + model: togetherai("meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"), + system: ` reply first time with the following text exactly as it is: ${contentString} + `, + messages: history.get(), + initial: ( + + + + ), + text: async function ({ content, done }) { + await sleep(1000); + if (done) { + history.done([...history.get(), { role: "assistant", content }]); + } + return {content}; + }, + }); + + return [{ + id: Date.now(), + role: "assistant" as const, + display: value.value + }] + } + + +const sleep = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/apps/app/actions/session.ts b/apps/app/actions/session.ts new file mode 100644 index 0000000..ce37587 --- /dev/null +++ b/apps/app/actions/session.ts @@ -0,0 +1,17 @@ +"use server" +import { betterFetch } from "@better-fetch/fetch"; +import { Session } from "@repo/auth"; +import { headers } from "next/headers"; + +export const getSession = async () => { + + const response = await betterFetch("http://localhost:3001/api/auth/get-session", + { + baseURL: "http://localhost:3002", + headers: { + cookie: (await headers()).get("cookie") || "", + }, + }); + return response.data + +} \ No newline at end of file diff --git a/apps/app/app/layout.tsx b/apps/app/app/layout.tsx index 5950190..3e45d3c 100644 --- a/apps/app/app/layout.tsx +++ b/apps/app/app/layout.tsx @@ -3,6 +3,7 @@ import { GeistSans } from "geist/font/sans"; import "./globals.css"; import { ThemeProvider } from "@/hooks/theme-provider"; import { PosthogProvider } from "@/hooks/posthog"; +import { AI } from "@/lib/ai"; export const metadata: Metadata = { title: "Plura", description: "Generated by create next app", @@ -15,22 +16,22 @@ async function RootLayout({ }>) { return ( - - - + + + - - {children} - - - - + {children} + + + + ); } diff --git a/apps/app/components/custom/onboarding/BeatLoader.tsx b/apps/app/components/custom/onboarding/BeatLoader.tsx new file mode 100644 index 0000000..f43132d --- /dev/null +++ b/apps/app/components/custom/onboarding/BeatLoader.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { motion } from "motion/react"; + +export default function BeatLoader() { + return ( +
+ {[0, 1, 2].map((index) => ( + + ))} +
+ ); +} diff --git a/apps/app/components/custom/onboarding/ChatInput.tsx b/apps/app/components/custom/onboarding/ChatInput.tsx new file mode 100644 index 0000000..44acf9c --- /dev/null +++ b/apps/app/components/custom/onboarding/ChatInput.tsx @@ -0,0 +1,35 @@ +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import {ArrowUp} from "lucide-react"; + +export function ChatInput() { + return ( +
+
+
+
+