From 85ecd9dac172cc4fc4049ba46be54aef84fb75f0 Mon Sep 17 00:00:00 2001 From: sibarchannayak Date: Fri, 29 May 2026 18:01:31 +0530 Subject: [PATCH] feat: implement AI service and prompt management pages for tenant configuration --- src/pages/tenant/PromptCreate.tsx | 61 ++++++++++++++++++++++++++- src/pages/tenant/PromptEdit.tsx | 60 +++++++++++++++++++++++++- src/pages/tenant/PromptManagement.tsx | 41 +++++++++++++++++- src/services/ai-service.ts | 4 +- src/types/ai.ts | 2 + 5 files changed, 161 insertions(+), 7 deletions(-) diff --git a/src/pages/tenant/PromptCreate.tsx b/src/pages/tenant/PromptCreate.tsx index 87055ee..b9b1941 100644 --- a/src/pages/tenant/PromptCreate.tsx +++ b/src/pages/tenant/PromptCreate.tsx @@ -9,9 +9,11 @@ import { SecondaryButton, FormSlider, FormTagInput, + MultiselectPaginatedSelect, } from "@/components/shared"; import { Plus, Trash2 } from "lucide-react"; import { aiService } from "@/services/ai-service"; +import { moduleService } from "@/services/module-service"; import { showToast } from "@/utils/toast"; import { useForm, useFieldArray, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -39,6 +41,7 @@ const promptSchema = z.object({ tags: z.array(z.string()), is_default: z.boolean(), variables: z.array(variableSchema), + module_ids: z.array(z.string().uuid()).optional(), }); type PromptFormData = z.infer; @@ -47,6 +50,7 @@ const PromptCreate = (): ReactElement => { const navigate = useNavigate(); const [isSubmitting, setIsSubmitting] = useState(false); const [apiProviders, setApiProviders] = useState([]); + const [modules, setModules] = useState>([]); const { control, @@ -76,6 +80,7 @@ const PromptCreate = (): ReactElement => { default: "", }, ], + module_ids: [], }, }); @@ -100,19 +105,53 @@ const PromptCreate = (): ReactElement => { useEffect(() => { const loadMeta = async (): Promise => { try { - const providerData = await aiService.getProviders(); + const [providerData, modulesRes] = await Promise.all([ + aiService.getProviders(), + moduleService.getMyModules(), + ]); setApiProviders(providerData); + setModules( + (modulesRes.data || []).map((m: any) => ({ + value: m.id, + label: m.name, + })) + ); } catch (err: unknown) { showToast.error( (err as { response?: { data?: { error?: { message?: string } } } }) ?.response?.data?.error?.message || - "Failed to load provider metadata", + "Failed to load provider or module metadata", ); } }; void loadMeta(); }, []); + const handleLoadModules = async ( + page: number, + limit: number, + search?: string + ) => { + let filtered = modules; + if (search) { + filtered = modules.filter((m) => + m.label.toLowerCase().includes(search.toLowerCase()) + ); + } + const startIndex = (page - 1) * limit; + const paginatedOptions = filtered.slice(startIndex, startIndex + limit); + return { + options: paginatedOptions, + pagination: { + page, + limit, + total: filtered.length, + totalPages: Math.ceil(filtered.length / limit), + hasMore: startIndex + limit < filtered.length, + }, + }; + }; + const onFormSubmit = async (data: PromptFormData): Promise => { const parsedTags = data.tags || []; @@ -140,6 +179,7 @@ const PromptCreate = (): ReactElement => { variables: sanitizedVariables, tags: parsedTags, is_default: data.is_default, + module_ids: data.module_ids, }); showToast.success("Prompt created successfully"); navigate("/tenant/ai/prompts"); @@ -559,6 +599,23 @@ const PromptCreate = (): ReactElement => {

Organization

+ ( + + )} + /> ; @@ -50,6 +53,7 @@ const PromptEdit = (): ReactElement => { const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [apiProviders, setApiProviders] = useState([]); + const [modules, setModules] = useState>([]); const [currentVersion, setCurrentVersion] = useState(1); const { @@ -75,6 +79,7 @@ const PromptEdit = (): ReactElement => { is_default: false, variables: [], change_notes: "", + module_ids: [], }, }); @@ -101,12 +106,19 @@ const PromptEdit = (): ReactElement => { if (!id) return; try { setIsLoading(true); - const [providerData, promptData] = await Promise.all([ + const [providerData, promptData, modulesRes] = await Promise.all([ aiService.getProviders(), aiService.getPrompt(id), + moduleService.getMyModules(), ]); setApiProviders(providerData); + setModules( + (modulesRes.data || []).map((m: any) => ({ + value: m.id, + label: m.name, + })) + ); reset({ name: promptData.name, @@ -127,6 +139,7 @@ const PromptEdit = (): ReactElement => { default: v.default ? String(v.default) : "", })), change_notes: "", + module_ids: promptData.moduleIds || [], }); setCurrentVersion(promptData.version || 1); } catch (err: unknown) { @@ -143,6 +156,31 @@ const PromptEdit = (): ReactElement => { void loadData(); }, [id, reset, navigate]); + const handleLoadModules = async ( + page: number, + limit: number, + search?: string + ) => { + let filtered = modules; + if (search) { + filtered = modules.filter((m) => + m.label.toLowerCase().includes(search.toLowerCase()) + ); + } + const startIndex = (page - 1) * limit; + const paginatedOptions = filtered.slice(startIndex, startIndex + limit); + return { + options: paginatedOptions, + pagination: { + page, + limit, + total: filtered.length, + totalPages: Math.ceil(filtered.length / limit), + hasMore: startIndex + limit < filtered.length, + }, + }; + }; + const onFormSubmit = async (data: PromptFormData): Promise => { if (!id) return; const parsedTags = data.tags || []; @@ -172,6 +210,7 @@ const PromptEdit = (): ReactElement => { tags: parsedTags, is_default: data.is_default, change_notes: data.change_notes, + module_ids: data.module_ids, }); showToast.success("Prompt updated successfully"); navigate("/tenant/ai/prompts"); @@ -601,6 +640,23 @@ const PromptEdit = (): ReactElement => {

Organization

+ ( + + )} + /> { totalPages: 1, }); + // Module filter states + const [modules, setModules] = useState>([]); + const [selectedModuleId, setSelectedModuleId] = useState(""); + // Modal states const [selectedPrompt, setSelectedPrompt] = useState(null); const [isVersionsOpen, setIsVersionsOpen] = useState(false); @@ -57,6 +63,24 @@ const PromptManagement = (): ReactElement => { return () => clearTimeout(timer); }, [search]); + // Load modules list on mount + useEffect(() => { + const loadModules = async (): Promise => { + try { + const res = await moduleService.getMyModules(); + setModules( + (res.data || []).map((m: any) => ({ + value: m.id, + label: m.name, + })) + ); + } catch (err: any) { + console.error("Failed to load modules list", err); + } + }; + void loadModules(); + }, []); + const loadPrompts = async (): Promise => { setIsLoading(true); setError(null); @@ -65,6 +89,7 @@ const PromptManagement = (): ReactElement => { page, limit, search: debouncedSearch || undefined, + module_id: selectedModuleId || undefined, }); setPrompts(result.data || []); setPagination({ @@ -86,7 +111,7 @@ const PromptManagement = (): ReactElement => { useEffect(() => { void loadPrompts(); - }, [page, limit, debouncedSearch]); + }, [page, limit, debouncedSearch, selectedModuleId]); const handleStatusToggle = async (prompt: AIPrompt) => { const newStatus = prompt.status === "active" ? "draft" : "active"; @@ -313,13 +338,25 @@ const PromptManagement = (): ReactElement => { }} >
-
+
+ {/*
*/} + { + setSelectedModuleId(val ? (Array.isArray(val) ? val[0] : val) : ""); + setPage(1); + }} + options={modules.map((m) => ({ value: m.value, label: m.label }))} + placeholder="All Modules" + /> + {/*
*/}
; tags?: string[]; is_default?: boolean; + module_ids?: string[]; }): Promise { const response = await apiClient.post("/ai/prompts", payload); return unwrap(response); @@ -173,6 +174,7 @@ class AIService { tags?: string[]; is_default?: boolean; change_notes?: string; + module_ids?: string[]; }): Promise { const response = await apiClient.put(`/ai/prompts/${id}`, payload); return unwrap(response); @@ -183,7 +185,7 @@ class AIService { return unwrap(response); } - async listPrompts(params: { page?: number; limit?: number; status?: string; search?: string } = {}): Promise<{ + async listPrompts(params: { page?: number; limit?: number; status?: string; search?: string; module_id?: string } = {}): Promise<{ data: AIPrompt[]; pagination?: { page: number; limit: number; total: number; totalPages: number }; }> { diff --git a/src/types/ai.ts b/src/types/ai.ts index fa6daae..f91f85a 100644 --- a/src/types/ai.ts +++ b/src/types/ai.ts @@ -127,6 +127,8 @@ export interface AIPrompt { createdAt?: string; updatedAt?: string; created_by_email?:string; + module_ids?: string[] | null; + moduleIds?: string[]; } export interface KnowledgeCollection {