feat(dimentorin): halaman Materi + AI Agent RAG

- /mentoring/materi: list materi (kategori chips, cards) + detail konten per slug
- /mentoring/ai-agent: chatbox RAG — tanya materi, jawaban dari Qdrant context + sumber relevansi %
- service: getMaterialsList/getMaterialBySlug/getMaterialById/getMaterialCategories + postChat + hooks
- header: menu Materi & AI Agent
- e2e verified: list 3 materi, detail tampil, chat 'ownership' jawab dari materi Rust + 3 sources (84/64/61%)
This commit is contained in:
asepharyana
2026-08-05 17:26:27 +07:00
parent 49a448fee1
commit 7294040fd1
7 changed files with 459 additions and 1 deletions
+49
View File
@@ -3,6 +3,11 @@ import {
TArticleDetail,
TArticleListItem,
TArticleListParams,
TChatRequest,
TChatResponse,
TMaterialDetail,
TMaterialItem,
TMaterialListResponse,
TBookSessionRequest,
TCreatePaymentRequest,
TMentorAvailability,
@@ -214,3 +219,47 @@ export const postRefreshPayment = async (
});
return data.data;
};
// ---- Materials ----
export const getMaterialsList = async (): Promise<TMaterialListResponse> => {
const { data } = await api({ method: 'GET', url: '/dimentorin/materials' });
return data.data;
};
export const getMaterialBySlug = async (
slug: string
): Promise<TMaterialDetail> => {
const { data } = await api({
method: 'GET',
url: `/dimentorin/materials/slug/${slug}`,
});
return data.data;
};
export const getMaterialById = async (
id: string
): Promise<TMaterialDetail> => {
const { data } = await api({ method: 'GET', url: `/dimentorin/materials/${id}` });
return data.data;
};
export const getMaterialCategories = async (): Promise<string[]> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/materials/categories',
});
return data.data;
};
// ---- AI Agent (RAG) ----
export const postChat = async (
request: TChatRequest
): Promise<TChatResponse> => {
const { data } = await api({
method: 'POST',
url: '/dimentorin/ai/chat',
data: request,
});
return data.data;
};
+62 -1
View File
@@ -12,7 +12,12 @@ import {
getMentorStats,
getMentors,
getMyPayments,
getMaterialById,
getMaterialBySlug,
getMaterialCategories,
getMaterialsList,
getMySessions,
postChat,
getPaymentById,
getSessionPayments,
postBookSession,
@@ -26,7 +31,12 @@ import {
TArticleDetail,
TArticleListItem,
TArticleListParams,
TBookSessionRequest,
TBookSessionRequest,
TChatRequest,
TChatResponse,
TMaterialDetail,
TMaterialItem,
TMaterialListResponse,
TCreatePaymentRequest,
TMentorAvailability,
TMentorDetail,
@@ -245,4 +255,55 @@ export const useGetArticleById = (
});
};
// ---- Materials ----
export const useGetMaterialsList = (): UseQueryResult<
TMaterialListResponse,
TResponseError
> => {
return useQuery({
queryKey: ['get-materials-list'],
queryFn: async () => await getMaterialsList(),
});
};
export const useGetMaterialBySlug = (
slug: string
): UseQueryResult<TMaterialDetail, TResponseError> => {
return useQuery({
queryKey: ['get-material-by-slug', slug],
queryFn: async () => await getMaterialBySlug(slug),
enabled: !!slug,
});
};
export const useGetMaterialById = (
id: string
): UseQueryResult<TMaterialDetail, TResponseError> => {
return useQuery({
queryKey: ['get-material-by-id', id],
queryFn: async () => await getMaterialById(id),
enabled: !!id,
});
};
export const useGetMaterialCategories = (): UseQueryResult<
string[],
TResponseError
> => {
return useQuery({
queryKey: ['get-material-categories'],
queryFn: async () => await getMaterialCategories(),
});
};
// ---- AI Agent ----
export const usePostChat = () => {
return useMutation({
mutationKey: ['post-chat'],
mutationFn: async (request: TChatRequest) => await postChat(request),
});
};
export type { TResponseMessage };
@@ -182,3 +182,50 @@ export type TPayment = {
export type TCreatePaymentRequest = {
method: string; // 'va' | 'qris' | 'manual'
};
export type TMaterialItem = {
id: string;
mentorId: string;
title: string;
slug: string;
category: string;
description: string;
coverUrl?: string | null;
isPublished: boolean;
createdAt: string;
updatedAt: string;
};
export type TMaterialDetail = TMaterialItem & {
content: string;
};
export type TMaterialListResponse = {
data: TMaterialItem[];
meta: {
page: number;
per_page: number;
total?: number;
total_pages?: number;
has_next: boolean;
has_prev: boolean;
};
};
export type TChatRequest = {
question: string;
materialId?: string;
};
export type TChatSource = {
materialId: string;
title: string;
score: number;
snippet: string;
};
export type TChatResponse = {
answer: string;
sources: TChatSource[];
};