diff --git a/apps/web/src/app/(app)/@admin/documents/table.tsx b/apps/web/src/app/(app)/@admin/documents/table.tsx
index 37413d1..5e8e7f3 100644
--- a/apps/web/src/app/(app)/@admin/documents/table.tsx
+++ b/apps/web/src/app/(app)/@admin/documents/table.tsx
@@ -184,7 +184,7 @@ export function DocumentsTable() {
(id ? id : crypto.randomUUID()), [id]);
+
+ const {
+ messages,
+ isLoading,
+ append: mutate,
+ } = useChat({
+ streamMode: "text",
+ onError: (error) => toast.error(error.message),
+ sendExtraMessageFields: true,
+ onFinish,
+ initialMessages,
+ id: chatId,
+ });
+ const chatContainerRef = useChatScroll(messages);
+
+ //#region //*=========== Form ===========
+ const methods = useForm({
+ mode: "onSubmit",
+ values: { prompt: value },
+ schema: z.object({
+ prompt: z.string().min(1, { message: "Please enter a message" }),
+ topic: z.string().optional(),
+ }),
+ });
+ const { handleSubmit, control, watch, setValue } = methods;
+ const onSubmit = handleSubmit(async (data) => {
+ setValue("prompt", "");
+ setValue("topic", "");
+ await mutate(
+ { content: data.prompt, role: "user" },
+ { options: { body: { topic: data.topic, chatId } } },
+ );
+ });
+ //#endregion //*======== Form ===========
+
+ const [dropdownOpen, setDropdownOpen] = React.useState(false);
+ const [suggestionsOpen, setSuggestionsOpen] = React.useState(false);
+ const [filteredTopics, setFilteredTopics] = React.useState(TOPICS);
+
+ const [debouncedPrompt] = useDebounceValue(watch("prompt"), 500);
+ const suggestions = api.chat.getSuggestion.useQuery(debouncedPrompt, {
+ enabled: !debouncedPrompt.startsWith("/") && debouncedPrompt.length > 0,
+ placeholderData: keepPreviousData,
+ });
+ const isAnySuggestion = suggestions.data && suggestions.data.length > 0;
+
+ return (
+ 0}
+ >
+
+
+ {messages.map((item) => (
+
+
+ {item.content.includes("") && item.content.includes(">") ? (
+
+ ) : (
+ item.content
+ )}
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/layout.tsx b/apps/web/src/app/(app)/layout.tsx
index 31cdb2c..4967518 100644
--- a/apps/web/src/app/(app)/layout.tsx
+++ b/apps/web/src/app/(app)/layout.tsx
@@ -42,7 +42,7 @@ export default async function AuthLayout(props: {
{isAdmin ? props.admin : props.user}
diff --git a/apps/web/src/server/api/routers/chat/chat.procedure.ts b/apps/web/src/server/api/routers/chat/chat.procedure.ts
index c762598..183624e 100644
--- a/apps/web/src/server/api/routers/chat/chat.procedure.ts
+++ b/apps/web/src/server/api/routers/chat/chat.procedure.ts
@@ -1,9 +1,42 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
+import { db } from "@/server/db";
import { chats, messages } from "@/server/db/schema";
import { and, desc, eq, ilike, notExists, or } from "drizzle-orm";
import { alias } from "drizzle-orm/pg-core";
import { z } from "zod";
+function getQuestion(input?: string) {
+ const m = alias(messages, "m");
+ return db
+ .select()
+ .from(m)
+ .where(
+ and(
+ eq(m.role, "user"),
+ ilike(m.content, `%${input}%`).if(input && input.length > 0),
+ notExists(
+ db
+ .select()
+ .from(messages)
+ .where(
+ and(
+ eq(messages.chatId, m.chatId),
+ eq(messages.role, "assistant"),
+ or(
+ ilike(messages.content, "%saya tidak tahu%"),
+ ilike(messages.content, "%hi %"),
+ ilike(messages.content, "%halo%"),
+ ),
+ ),
+ )
+ .orderBy(desc(messages.createdAt)),
+ ),
+ ),
+ )
+ .orderBy(desc(m.createdAt))
+ .limit(3);
+}
+
export const chatRouter = createTRPCRouter({
get: protectedProcedure
.input(
@@ -29,35 +62,14 @@ export const chatRouter = createTRPCRouter({
});
}),
- getRecentQuestion: protectedProcedure.query(async ({ ctx }) => {
- const m = alias(messages, "m");
- return await ctx.db
- .select()
- .from(m)
- .where(
- and(
- eq(m.role, "user"),
- notExists(
- ctx.db
- .select()
- .from(messages)
- .where(
- and(
- eq(messages.chatId, m.chatId),
- eq(messages.role, "assistant"),
- or(
- ilike(messages.content, "%saya tidak tahu%"),
- ilike(messages.content, "%hi %"),
- ilike(messages.content, "%halo%"),
- ),
- ),
- )
- .orderBy(desc(messages.createdAt)),
- ),
- ),
- )
- .orderBy(desc(m.createdAt))
- .limit(3);
+ getSuggestion: protectedProcedure
+ .input(z.string())
+ .query(async ({ input }) => {
+ return await getQuestion(input);
+ }),
+
+ getRecentQuestion: protectedProcedure.query(async () => {
+ return await getQuestion();
}),
show: protectedProcedure
diff --git a/packages/ui/src/chat.tsx b/packages/ui/src/chat.tsx
index 05994b2..f7412e4 100644
--- a/packages/ui/src/chat.tsx
+++ b/packages/ui/src/chat.tsx
@@ -21,7 +21,7 @@ import { Form, FormTextArea, useForm } from "./form";
const TOPICS = ["template", "MBKM", "SKEM", "UKT", "TA", "wisuda", "silabus"];
-function useChatScroll(dep: T) {
+export function useChatScroll(dep: T) {
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current) {