Skip to content

Commit

Permalink
Update - AI assistant - cron
Browse files Browse the repository at this point in the history
- remaster AI assistant API endpoint

Update - Administration
- added settings for GPT model
  • Loading branch information
pdovhomilja committed Oct 12, 2023
1 parent 8d32e4b commit e61b254
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 135 deletions.
23 changes: 23 additions & 0 deletions actions/admin/update-gpt-model.ts
Original file line number Diff line number Diff line change
@@ -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;
144 changes: 144 additions & 0 deletions actions/cron/get-user-ai-tasks.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
42 changes: 42 additions & 0 deletions app/[locale]/(routes)/admin/_components/GptCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Card className="w-1/3">
<CardHeader className="text-lg">
<CardTitle>AI assistant GPT model</CardTitle>
<CardDescription>
<div>
actual model:{" "}
{
//filter in gptModels where status = ACTIVE
gptModels
.filter((model) => model.status === "ACTIVE")
.map((model) => model.model)
}
</div>
</CardDescription>
</CardHeader>
<CardContent className="space-y-2">
<SetGptModel models={gptModels} />
<OnTestButton />
</CardContent>
</Card>
);
};

export default GptCard;
43 changes: 43 additions & 0 deletions app/[locale]/(routes)/admin/_components/OnTestButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col space-y-2">
<Label>Send test</Label>
<Button onClick={onTest} disabled={loading}>
{loading ? <Icons.spinner className="animate-spin" /> : "Test"}
</Button>
</div>
);
};

export default OnTestButton;
90 changes: 90 additions & 0 deletions app/[locale]/(routes)/admin/forms/SetGptModel.tsx
Original file line number Diff line number Diff line change
@@ -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<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});

async function onSubmit(data: z.infer<typeof FormSchema>) {
try {
await updateModel(data.model);
toast({
title: "GPT model updated",
});
} catch (error) {
console.log(error);
} finally {
router.refresh();
}
}

return (
<Form {...form}>
{/* <pre>{JSON.stringify(form.watch(), null, 2)}</pre> */}
<form
onSubmit={form.handleSubmit(onSubmit)}
className=" space-y-6 flex space-x-3 items-center justify-center"
>
<FormField
control={form.control}
name="model"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>GPT model</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a gpt model for AI assistant" />
</SelectTrigger>
</FormControl>
<SelectContent>
{models.map((model: any) => (
<SelectItem key={model.id} value={model.id}>
{model.model}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
};

export default SetGptModel;
14 changes: 9 additions & 5 deletions app/[locale]/(routes)/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -23,7 +24,7 @@ const AdminPage = async () => {

return (
<Container
title="Administation"
title="Administration"
description={"Here you can setup your NextCRM instance"}
>
<div className="space-x-2">
Expand All @@ -34,6 +35,9 @@ const AdminPage = async () => {
<Link href="/admin/modules">Modules administration</Link>
</Button>
</div>
<div>
<GptCard />
</div>
</Container>
);
};
Expand Down
Loading

6 comments on commit e61b254

@vercel
Copy link

@vercel vercel bot commented on e61b254 Oct 12, 2023

Choose a reason for hiding this comment

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

@vercel
Copy link

@vercel vercel bot commented on e61b254 Oct 12, 2023

Choose a reason for hiding this comment

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

@vercel
Copy link

@vercel vercel bot commented on e61b254 Oct 12, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

nextcrm-test – ./

nextcrm-test-git-main-e-osvc.vercel.app
test.nextcrm.io
nextcrm-test-e-osvc.vercel.app

@vercel
Copy link

@vercel vercel bot commented on e61b254 Oct 12, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

nextcrm-others – ./

nextcrm-others-git-main-e-osvc.vercel.app
nextcrm-others-e-osvc.vercel.app
others.nextcrm.io

@vercel
Copy link

@vercel vercel bot commented on e61b254 Oct 12, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

nextcrm-demo – ./

nextcrm-demo-e-osvc.vercel.app
demo.nextcrm.io
nextcrm-demo-git-main-e-osvc.vercel.app

@vercel
Copy link

@vercel vercel bot commented on e61b254 Oct 12, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.