From 512ae7835554412f5193241ebbe2fee879cf74d4 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 21 May 2025 18:20:06 +0200 Subject: [PATCH] refactor(chatbot-config): split client logic into separate component for SSR compatibility --- ui/app/(prowler)/lighthouse/config/page.tsx | 274 +----------------- ui/app/(prowler)/lighthouse/page.tsx | 9 +- .../lighthouse/chat.tsx | 6 +- ui/components/lighthouse/chatbot-config.tsx | 188 ++++++++++++ ui/components/lighthouse/index.ts | 2 + 5 files changed, 215 insertions(+), 264 deletions(-) rename ui/{app/(prowler) => components}/lighthouse/chat.tsx (98%) create mode 100644 ui/components/lighthouse/chatbot-config.tsx create mode 100644 ui/components/lighthouse/index.ts diff --git a/ui/app/(prowler)/lighthouse/config/page.tsx b/ui/app/(prowler)/lighthouse/config/page.tsx index 88e71120cf..ebf1423009 100644 --- a/ui/app/(prowler)/lighthouse/config/page.tsx +++ b/ui/app/(prowler)/lighthouse/config/page.tsx @@ -1,268 +1,26 @@ -"use client"; +import { getLighthouseConfig } from "@/actions/lighthouse"; +import { ChatbotConfig } from "@/components/lighthouse"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Select, SelectItem, Spacer } from "@nextui-org/react"; -import { SaveIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import * as z from "zod"; +export const dynamic = "force-dynamic"; -import { - createLighthouseConfig, - getLighthouseConfig, - updateLighthouseConfig, -} from "@/actions/lighthouse"; -import { useToast } from "@/components/ui"; -import { - CustomButton, - CustomInput, - CustomTextarea, -} from "@/components/ui/custom"; -import { Form } from "@/components/ui/form"; +export default async function ChatbotConfigPage() { + const response = await getLighthouseConfig(); -const chatbotConfigSchema = z.object({ - model: z.string().nonempty("Model selection is required"), - apiKey: z.string().nonempty("API Key is required").optional(), // Make optional for initial loading - businessContext: z - .string() - .max(1000, "Business context cannot exceed 1000 characters") - .optional(), -}); - -type FormValues = z.infer; - -export default function ChatbotConfig() { - const { toast } = useToast(); - const [isLoading, setIsLoading] = useState(false); - const [isFetching, setIsFetching] = useState(true); - const [configExists, setConfigExists] = useState(false); - - // Create form with more lenient validation for initial load - const form = useForm({ - resolver: zodResolver(chatbotConfigSchema), - defaultValues: { - model: "gpt-4o", - apiKey: "", - businessContext: "", - }, - mode: "onChange", // Add this to ensure form updates immediately - }); - - // Add a useEffect to log when form values change - useEffect(() => { - const subscription = form.watch((value, { name, type }) => { - if (name && type) { - // Only log when we have valid change info - console.log( - `Form value changed: ${name} = ${JSON.stringify(value)}, type = ${type}`, - ); + const initialValues = response?.data?.attributes + ? { + model: response.data.attributes.model, + apiKey: response.data.attributes.api_key || "", + businessContext: response.data.attributes.business_context || "", } - }); - - return () => subscription.unsubscribe(); - }, [form]); - - // Fetch existing configuration using server action - useEffect(() => { - let isMounted = true; - - async function loadConfiguration() { - setIsFetching(true); - - try { - const response = await getLighthouseConfig(); - - if (!isMounted) return; - - if (!response) { - setConfigExists(false); - return; - } - - if (response.data?.attributes) { - setConfigExists(true); - const attrs = response.data.attributes; - form.reset({ - model: attrs.model, - apiKey: attrs.api_key || "", - businessContext: attrs.business_context || "", - }); - } - } catch (error) { - if (isMounted) { - setConfigExists(false); - toast({ - title: "Error", - description: "Failed to load configuration: " + String(error), - variant: "destructive", - }); - } - } finally { - if (isMounted) { - setIsFetching(false); - } - } - } - - loadConfiguration(); - - return () => { - isMounted = false; // Cleanup function to flag unmount - }; - }, []); - - const onSubmit = async (data: FormValues) => { - if (isLoading) return; // Prevent duplicate submissions - setIsLoading(true); - try { - // Create base config without API key - const configData: any = { - model: data.model, - businessContext: data.businessContext || "", + : { + model: "gpt-4o", + apiKey: "", + businessContext: "", }; - // Only include API key if it's provided and doesn't contain asterisks - if (data.apiKey && !data.apiKey.includes("*")) { - configData.apiKey = data.apiKey; - } - - // Conditionally use create or update based on whether configuration exists - const result = configExists - ? await updateLighthouseConfig(configData) - : await createLighthouseConfig(configData); - - console.log("Operation result:", result); - - if (result) { - // Set configExists to true after successful creation - if (!configExists) { - setConfigExists(true); - } - - toast({ - title: "Success", - description: `Lighthouse configuration ${configExists ? "updated" : "created"} successfully`, - }); - } else { - throw new Error("Failed to save configuration"); - } - } catch (error) { - toast({ - title: "Error", - description: - "Failed to save lighthouse configuration: " + String(error), - variant: "destructive", - }); - console.error(error); - } finally { - setIsLoading(false); - } - }; - - if (isFetching) { - return ( -
-
-
-

- Loading configuration... -

-
-
-
- ); - } + const configExists = !!response; return ( -
-

Chatbot Settings

-

- Configure your chatbot model and API settings. -

- -
- - {/* Model Selection */} - ( - - )} - /> - - - - {/* API Key Input */} - - - - - {/* Business Context Textarea */} - - - - - {/* Save Button */} -
- } - > - {isLoading ? "Saving..." : "Save"} - -
- - -
+ ); } diff --git a/ui/app/(prowler)/lighthouse/page.tsx b/ui/app/(prowler)/lighthouse/page.tsx index 4de467dec6..3b814644aa 100644 --- a/ui/app/(prowler)/lighthouse/page.tsx +++ b/ui/app/(prowler)/lighthouse/page.tsx @@ -1,9 +1,8 @@ import { getAIKey } from "@/actions/lighthouse/lighthouse"; +import { Chat } from "@/components/lighthouse"; import { ContentLayout } from "@/components/ui"; -import Chat from "./chat"; - -export default async function AIChatbot() { +export const AIChatbot = async () => { const apiKey = await getAIKey(); return ( @@ -11,4 +10,6 @@ export default async function AIChatbot() { ); -} +}; + +export default AIChatbot; diff --git a/ui/app/(prowler)/lighthouse/chat.tsx b/ui/components/lighthouse/chat.tsx similarity index 98% rename from ui/app/(prowler)/lighthouse/chat.tsx rename to ui/components/lighthouse/chat.tsx index f47916c454..1bbffa6111 100644 --- a/ui/app/(prowler)/lighthouse/chat.tsx +++ b/ui/components/lighthouse/chat.tsx @@ -17,7 +17,7 @@ interface ChatProps { hasApiKey: boolean; } -export default function Chat({ hasApiKey }: ChatProps) { +export const Chat = ({ hasApiKey }: ChatProps) => { const { messages, input, handleSubmit, handleInputChange, append, status } = useChat({ api: "/api/lighthouse/analyst", @@ -211,4 +211,6 @@ export default function Chat({ hasApiKey }: ChatProps) { ); -} +}; + +export default Chat; diff --git a/ui/components/lighthouse/chatbot-config.tsx b/ui/components/lighthouse/chatbot-config.tsx new file mode 100644 index 0000000000..d39652deba --- /dev/null +++ b/ui/components/lighthouse/chatbot-config.tsx @@ -0,0 +1,188 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Select, SelectItem, Spacer } from "@nextui-org/react"; +import { SaveIcon } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import * as z from "zod"; + +import { + createLighthouseConfig, + updateLighthouseConfig, +} from "@/actions/lighthouse"; +import { useToast } from "@/components/ui"; +import { + CustomButton, + CustomInput, + CustomTextarea, +} from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; + +const chatbotConfigSchema = z.object({ + model: z.string().nonempty("Model selection is required"), + apiKey: z.string().nonempty("API Key is required").optional(), + businessContext: z + .string() + .max(1000, "Business context cannot exceed 1000 characters") + .optional(), +}); + +type FormValues = z.infer; + +interface ChatbotConfigClientProps { + initialValues: FormValues; + configExists: boolean; +} + +export const ChatbotConfig = ({ + initialValues, + configExists: initialConfigExists, +}: ChatbotConfigClientProps) => { + const { toast } = useToast(); + const [isLoading, setIsLoading] = useState(false); + const [configExists, setConfigExists] = useState(initialConfigExists); + + const form = useForm({ + resolver: zodResolver(chatbotConfigSchema), + defaultValues: initialValues, + mode: "onChange", + }); + + useEffect(() => { + const subscription = form.watch((value, { name, type }) => { + if (name && type) { + console.log(`Form value changed: ${name} = ${JSON.stringify(value)}`); + } + }); + return () => subscription.unsubscribe(); + }, [form]); + + const onSubmit = async (data: FormValues) => { + if (isLoading) return; + setIsLoading(true); + try { + const configData: any = { + model: data.model, + businessContext: data.businessContext || "", + }; + if (data.apiKey && !data.apiKey.includes("*")) { + configData.apiKey = data.apiKey; + } + + const result = configExists + ? await updateLighthouseConfig(configData) + : await createLighthouseConfig(configData); + + if (result) { + setConfigExists(true); + toast({ + title: "Success", + description: `Lighthouse configuration ${ + configExists ? "updated" : "created" + } successfully`, + }); + } else { + throw new Error("Failed to save configuration"); + } + } catch (error) { + toast({ + title: "Error", + description: + "Failed to save lighthouse configuration: " + String(error), + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + return ( +
+

Chatbot Settings

+

+ Configure your chatbot model and API settings. +

+ +
+ + ( + + )} + /> + + + + + + + + + + + +
+ } + > + {isLoading ? "Saving..." : "Save"} + +
+ + +
+ ); +}; diff --git a/ui/components/lighthouse/index.ts b/ui/components/lighthouse/index.ts new file mode 100644 index 0000000000..a56f7c4fbc --- /dev/null +++ b/ui/components/lighthouse/index.ts @@ -0,0 +1,2 @@ +export * from "./chat"; +export * from "./chatbot-config";