diff --git a/actions/admin/update-gpt-model.ts b/actions/admin/update-gpt-model.ts new file mode 100644 index 0000000..bd4de93 --- /dev/null +++ b/actions/admin/update-gpt-model.ts @@ -0,0 +1,23 @@ +"use server"; + +import { prismadb } from "@/lib/prisma"; + +const updateModel = async (model: any) => { + await prismadb.gpt_models.updateMany({ + data: { + status: "INACTIVE", + }, + }); + + const setCronGPT = await prismadb.gpt_models.update({ + where: { + id: model, + }, + data: { + status: "ACTIVE", + }, + }); + console.log("change GPT model to:", setCronGPT); +}; + +export default updateModel; diff --git a/actions/cron/get-user-ai-tasks.ts b/actions/cron/get-user-ai-tasks.ts new file mode 100644 index 0000000..8503795 --- /dev/null +++ b/actions/cron/get-user-ai-tasks.ts @@ -0,0 +1,144 @@ +import dayjs from "dayjs"; +import axios from "axios"; + +import sendEmail from "@/lib/sendmail"; +import { prismadb } from "@/lib/prisma"; + +export async function getUserAiTasks(userId: string) { + const today = dayjs().startOf("day"); + const nextWeek = dayjs().add(7, "day").startOf("day"); + + let prompt = ""; + + const user = await prismadb.users.findUnique({ + where: { + id: userId, + }, + }); + + if (!user) return { message: "No user found" }; + + const getTaskPastDue = await prismadb.tasks.findMany({ + where: { + AND: [ + { + user: userId, + taskStatus: "ACTIVE", + dueDateAt: { + lte: new Date(), + }, + }, + ], + }, + }); + + const getTaskPastDueInSevenDays = await prismadb.tasks.findMany({ + where: { + AND: [ + { + user: userId, + taskStatus: "ACTIVE", + dueDateAt: { + //lte: dayjs().add(7, "day").toDate(), + gt: today.toDate(), // Due date is greater than or equal to today + lt: nextWeek.toDate(), // Due date is less than next week (not including today) + }, + }, + ], + }, + }); + + if (!getTaskPastDue || !getTaskPastDueInSevenDays) { + return { message: "No tasks found" }; + } + + switch (user.userLanguage) { + case "en": + prompt = `Hi, Iam ${process.env.NEXT_PUBLIC_APP_URL} API Bot. + \n\n + There are ${getTaskPastDue.length} tasks past due and ${ + getTaskPastDueInSevenDays.length + } tasks due in the next 7 days. + \n\n + Details today tasks: ${JSON.stringify(getTaskPastDue, null, 2)} + \n\n + Details next 7 days tasks: ${JSON.stringify( + getTaskPastDueInSevenDays, + null, + 2 + )} + \n\n + As a personal assistant, write a message to ${ + user.name + } to remind them of their tasks. And also do not forget to send them a some positive vibes. + \n\n + `; + break; + case "cz": + prompt = `Jako profesionální asistentka Emma s perfektní znalostí projektového řízení, který má na starosti projekty na adrese${ + process.env.NEXT_PUBLIC_APP_URL + }, připrave manažerské shrnutí o úkolech včetně jejich detailů a termínů. Vše musí být perfektně česky a výstižně. + \n\n + Zde jsou informace k úkolům: + \n\n + Informace o projektu: Počet úkolů které jsou k řešení dnes: ${ + getTaskPastDue.length + }, Počet úkolů, které musí být vyřešeny nejpozději do sedmi dnů: ${ + getTaskPastDueInSevenDays.length + }. + \n\n + Detailní informace v JSON formátu k úkolům, které musí být hotové dnes: ${JSON.stringify( + getTaskPastDue, + null, + 2 + )} + \n\n + Detailní informace k úkolům, které musí být hotové během následujících sedmi dní: ${JSON.stringify( + getTaskPastDueInSevenDays, + null, + 2 + )} + + \n\n + Na konec napiš manažerské shrnutí včetně milého uvítání napiš pro uživatele: ${ + user.name + } a přidej odkaz ${ + process.env.NEXT_PUBLIC_APP_URL + "/projects/dashboard" + } jako odkaz na detail k úkolům . Na konci manažerského shrnutí přidej. 1 tip na manažerskou dovednost z oblasti projektového řízení a timemanagementu, 2-3 věty s pozitivním naladěním a podporou, nakonec popřej hezký pracovní den a infomaci, že tato zpráva byla vygenerována pomocí umělé inteligence OpenAi. + \n\n + `; + break; + } + + if (!prompt) return { message: "No prompt found" }; + + const getAiResponse = await axios + .post( + `${process.env.NEXT_PUBLIC_APP_URL}/api/openai/create-chat-completion`, + { + prompt: prompt, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ) + .then((res) => res.data); + + //console.log(getAiResponse, "getAiResponse"); + + //skip if api response is error + if (getAiResponse.error) { + console.log("Error from OpenAI API"); + } else { + await sendEmail({ + from: process.env.EMAIL_FROM, + to: user.email!, + subject: `${process.env.NEXT_PUBLIC_APP_NAME} OpenAI Project manager assistant`, + text: getAiResponse.response.message.content, + }); + } + + return { user: user.email }; +} diff --git a/app/[locale]/(routes)/admin/_components/GptCard.tsx b/app/[locale]/(routes)/admin/_components/GptCard.tsx new file mode 100644 index 0000000..9f8d8bf --- /dev/null +++ b/app/[locale]/(routes)/admin/_components/GptCard.tsx @@ -0,0 +1,42 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +import { prismadb } from "@/lib/prisma"; +import SetGptModel from "../forms/SetGptModel"; + +import OnTestButton from "./OnTestButton"; + +const GptCard = async () => { + const gptModels = await prismadb.gpt_models.findMany(); + //console.log(gptModels, "gptModels"); + + return ( + + + AI assistant GPT model + +
+ actual model:{" "} + { + //filter in gptModels where status = ACTIVE + gptModels + .filter((model) => model.status === "ACTIVE") + .map((model) => model.model) + } +
+
+
+ + + + +
+ ); +}; + +export default GptCard; diff --git a/app/[locale]/(routes)/admin/_components/OnTestButton.tsx b/app/[locale]/(routes)/admin/_components/OnTestButton.tsx new file mode 100644 index 0000000..0b14019 --- /dev/null +++ b/app/[locale]/(routes)/admin/_components/OnTestButton.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { Icons } from "@/components/ui/icons"; +import { Label } from "@/components/ui/label"; +import { useToast } from "@/components/ui/use-toast"; +import axios from "axios"; +import React from "react"; + +const OnTestButton = () => { + const [loading, setLoading] = React.useState(false); + const { toast } = useToast(); + + async function onTest() { + setLoading(true); + try { + const response = await axios.get("/api/cron/send-daily-task-ai"); + //console.log(response, "response"); + toast({ + title: "GPT model tested", + description: response.data.message, + }); + } catch (error) { + console.log(error); + toast({ + variant: "destructive", + title: "GPT model test failed", + }); + } finally { + setLoading(false); + } + } + return ( +
+ + +
+ ); +}; + +export default OnTestButton; diff --git a/app/[locale]/(routes)/admin/forms/SetGptModel.tsx b/app/[locale]/(routes)/admin/forms/SetGptModel.tsx new file mode 100644 index 0000000..12a25ab --- /dev/null +++ b/app/[locale]/(routes)/admin/forms/SetGptModel.tsx @@ -0,0 +1,90 @@ +"use client"; + +import Link from "next/link"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { toast } from "@/components/ui/use-toast"; +import updateModel from "@/actions/admin/update-gpt-model"; +import { useRouter } from "next/navigation"; + +const FormSchema = z.object({ + model: z.string().min(10).max(30), +}); + +const SetGptModel = ({ models }: any) => { + const router = useRouter(); + + const form = useForm>({ + resolver: zodResolver(FormSchema), + }); + + async function onSubmit(data: z.infer) { + try { + await updateModel(data.model); + toast({ + title: "GPT model updated", + }); + } catch (error) { + console.log(error); + } finally { + router.refresh(); + } + } + + return ( +
+ {/*
{JSON.stringify(form.watch(), null, 2)}
*/} + + ( + + GPT model + + + + )} + /> + + + + ); +}; + +export default SetGptModel; diff --git a/app/[locale]/(routes)/admin/page.tsx b/app/[locale]/(routes)/admin/page.tsx index 97a4bc5..a6065ab 100644 --- a/app/[locale]/(routes)/admin/page.tsx +++ b/app/[locale]/(routes)/admin/page.tsx @@ -1,9 +1,10 @@ -import Heading from "@/components/ui/heading"; -import { Separator } from "@/components/ui/separator"; +import Link from "next/link"; + import { getUser } from "@/actions/get-user"; -import Container from "../components/ui/Container"; + import { Button } from "@/components/ui/button"; -import Link from "next/link"; +import Container from "../components/ui/Container"; +import GptCard from "./_components/GptCard"; const AdminPage = async () => { const user = await getUser(); @@ -23,7 +24,7 @@ const AdminPage = async () => { return (
@@ -34,6 +35,9 @@ const AdminPage = async () => { Modules administration
+
+ +
); }; diff --git a/app/api/cron/send-daily-task-ai/route.ts b/app/api/cron/send-daily-task-ai/route.ts index 6133bf8..116bd08 100644 --- a/app/api/cron/send-daily-task-ai/route.ts +++ b/app/api/cron/send-daily-task-ai/route.ts @@ -1,20 +1,22 @@ /* -This API endpoint is used to add a new task to a section. +This API endpoint is used to create a cron job that will send an email to all users with their tasks for the day and the next 7 days. */ +import { getUserAiTasks } from "@/actions/cron/get-user-ai-tasks"; import { prismadb } from "@/lib/prisma"; -import sendEmail from "@/lib/sendmail"; -import { data } from "autoprefixer"; -import axios from "axios"; -import dayjs from "dayjs"; import { NextResponse } from "next/server"; export async function GET(req: Request) { - /* try { - const today = dayjs().startOf("day"); - const nextWeek = dayjs().add(7, "day").startOf("day"); - let prompt = ""; + /* +This endpoint is not available in the demo version of NextCRM. +*/ + if (process.env.NEXT_PUBLIC_APP_URL === "demo.nextcrm.io") { + return NextResponse.json({ + message: "AI assistant is not available in Demo version", + }); + } + try { const users = await prismadb.users.findMany({ where: { userStatus: "ACTIVE", @@ -24,131 +26,18 @@ export async function GET(req: Request) { if (!users) return NextResponse.json({ message: "No users found" }); for (const user of users) { - const getTaskPastDue = await prismadb.tasks.findMany({ - where: { - AND: [ - { - user: user.id, - }, - { - dueDateAt: { - lte: new Date(), - }, - }, - ], - }, - }); - - const getTaskPastDueInSevenDays = await prismadb.tasks.findMany({ - where: { - AND: [ - { - user: user.id, - }, - { - dueDateAt: { - //lte: dayjs().add(7, "day").toDate(), - gt: today.toDate(), // Due date is greater than or equal to today - lt: nextWeek.toDate(), // Due date is less than next week (not including today) - }, - }, - ], - }, - }); - - //console.log(user.userLanguage, "users.userLanguage"); - switch (user.userLanguage) { - case "en": - prompt = `Hi, Iam ${process.env.NEXT_PUBLIC_APP_URL} API Bot. - \n\n - There are ${getTaskPastDue.length} tasks past due and ${ - getTaskPastDueInSevenDays.length - } tasks due in the next 7 days. - \n\n - Details today tasks: ${JSON.stringify(getTaskPastDue, null, 2)} - \n\n - Details next 7 days tasks: ${JSON.stringify( - getTaskPastDueInSevenDays, - null, - 2 - )} - \n\n - As a personal assistant, write a message to ${ - user.name - } to remind them of their tasks. And also do not forget to send them a some positive vibes. - \n\n - `; - break; - case "cz": - prompt = `Jako profesionální asistentka Emma s perfektní znalostí projektového řízení, který má na starosti projekty na adrese${ - process.env.NEXT_PUBLIC_APP_URL - }, připrave manažerské shrnutí o úkolech včetně jejich detailů a termínů. Vše musí být perfektně česky a výstižně. - \n\n - Zde jsou informace k úkolům: - \n\n - Informace o projektu: Počet úkolů které jsou k řešení dnes: ${ - getTaskPastDue.length - }, Počet úkolů, které musí být vyřešeny nejpozději do sedmi dnů: ${ - getTaskPastDueInSevenDays.length - }. - \n\n - Detailní informace v JSON formátu k úkolům, které musí být hotové dnes: ${JSON.stringify( - getTaskPastDue, - null, - 2 - )} - \n\n - Detailní informace k úkolům, které musí být hotové během následujících sedmi dní: ${JSON.stringify( - getTaskPastDueInSevenDays, - null, - 2 - )} - - \n\n - Na konec napiš manažerské shrnutí včetně milého uvítání napiš pro uživatele: ${ - user.name - } a přidej odkaz ${ - process.env.NEXT_PUBLIC_APP_URL + "/projects/dashboard" - } jako odkaz na detail k úkolům . Na konci manažerského shrnutí přidej. 1 tip na manažerskou dovednost z oblasti projektového řízení a timemanagementu, 2-3 věty s pozitivním naladěním a podporou, nakonec popřej hezký pracovní den a infomaci, že tato zpráva byla vygenerována pomocí umělé inteligence OpenAi. - \n\n - `; - break; + const action = await getUserAiTasks(user.id); + if (action.message!) { + console.log(action.message); + return NextResponse.json({ message: action.message }); } - - if (!prompt) return NextResponse.json({ message: "No prompt found" }); - - const getAiResponse = await fetch( - `${process.env.NEXT_PUBLIC_APP_URL}/api/openai/create-chat-completion`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - prompt: prompt, - }), - } - ).then((res) => res.json()); - - console.log(getAiResponse.response, "getAiResponse"); - - //skip if api response is error - if (getAiResponse.error) { - console.log("Error from OpenAI API"); - } else { - await sendEmail({ - from: process.env.EMAIL_FROM, - to: user.email!, - subject: `${process.env.NEXT_PUBLIC_APP_NAME} OpenAI Project manager assistant`, - text: getAiResponse.response.message.content, - }); + if (action.user) { + console.log("Emails sent to:", action.user); + return NextResponse.json({ message: "Emails sent to:" + action.user }); } } - - return NextResponse.json({ message: "Emails sent" }); } catch (error) { console.log("[TASK_CRON_API]", error); return new NextResponse("Initial error", { status: 500 }); - } */ - return new NextResponse("Initial error", { status: 500 }); + } } diff --git a/app/api/databox/route.ts b/app/api/databox/route.ts new file mode 100644 index 0000000..fd1c93d --- /dev/null +++ b/app/api/databox/route.ts @@ -0,0 +1,17 @@ +import { authOptions } from "@/lib/auth"; +import { getServerSession } from "next-auth"; +import { NextResponse } from "next/server"; + +export async function GET(req: Request, res: Response) { + const session = await getServerSession(authOptions); + if (!session) { + return NextResponse.json({ message: "unauthorized" }, { status: 401 }); + } + try { + console.log("This endpoint works!"); + return NextResponse.json({ message: "ok" }, { status: 200 }); + } catch (error) { + console.log(error); + return NextResponse.json({ message: "error" }, { status: 500 }); + } +}