"use client"; import { useChat } from "@ai-sdk/react"; import Link from "next/link"; import { useEffect, useRef } from "react"; import { useForm } from "react-hook-form"; import { MemoizedMarkdown } from "@/components/lighthouse/memoized-markdown"; import { CustomButton, CustomTextarea } from "@/components/ui/custom"; import { Form } from "@/components/ui/form"; interface SuggestedAction { title: string; label: string; action: string; } interface ChatProps { hasConfig: boolean; isActive: boolean; } interface ChatFormData { message: string; } export const Chat = ({ hasConfig, isActive }: ChatProps) => { const { messages, handleSubmit, handleInputChange, append, status } = useChat( { api: "/api/lighthouse/analyst", credentials: "same-origin", experimental_throttle: 100, sendExtraMessageFields: true, onFinish: () => { // Handle chat completion }, onError: (error) => { console.error("Chat error:", error); }, }, ); const form = useForm({ defaultValues: { message: "", }, }); const messageValue = form.watch("message"); const messagesContainerRef = useRef(null); const latestUserMsgRef = useRef(null); // Sync form value with chat input useEffect(() => { const syntheticEvent = { target: { value: messageValue }, } as React.ChangeEvent; handleInputChange(syntheticEvent); }, [messageValue, handleInputChange]); // Reset form when message is sent useEffect(() => { if (status === "submitted") { form.reset({ message: "" }); } }, [status, form]); const onFormSubmit = form.handleSubmit((data) => { if (data.message.trim()) { handleSubmit(); } }); // Global keyboard shortcut handler useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); if (messageValue?.trim()) { onFormSubmit(); } } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [messageValue, onFormSubmit]); useEffect(() => { if (messagesContainerRef.current && latestUserMsgRef.current) { const container = messagesContainerRef.current; const userMsg = latestUserMsgRef.current; const containerPadding = 16; // p-4 in Tailwind = 16px container.scrollTop = userMsg.offsetTop - container.offsetTop - containerPadding; } }, [messages]); const suggestedActions: SuggestedAction[] = [ { title: "Are there any exposed S3", label: "buckets in my AWS accounts?", action: "List exposed S3 buckets in my AWS accounts", }, { title: "What is the risk of having", label: "RDS databases unencrypted?", action: "What is the risk of having RDS databases unencrypted?", }, { title: "What is the CIS 1.10 compliance status", label: "of my Kubernetes cluster?", action: "What is the CIS 1.10 compliance status of my Kubernetes cluster?", }, { title: "List my highest privileged", label: "AWS IAM users with full admin access?", action: "List my highest privileged AWS IAM users with full admin access", }, ]; // Determine if chat should be disabled const shouldDisableChat = !hasConfig || !isActive; return (
{shouldDisableChat && (

{!hasConfig ? "OpenAI API Key Required" : "OpenAI API Key Invalid"}

{!hasConfig ? "Please configure your OpenAI API key to use the Lighthouse Cloud Security Analyst." : "OpenAI API key is invalid. Please update your key to use Lighthouse Cloud Security Analyst."}

Configure API Key
)} {messages.length === 0 ? (

Suggestions

{suggestedActions.map((action, index) => ( { append({ role: "user", content: action.action, }); }} className="hover:bg-muted flex h-auto w-full flex-col items-start justify-start rounded-xl border bg-gray-50 px-4 py-3.5 text-left font-sans text-sm dark:bg-gray-900" > {action.title} {action.label} ))}
) : (
{messages.map((message, idx) => { const lastUserIdx = messages .map((m, i) => (m.role === "user" ? i : -1)) .filter((i) => i !== -1) .pop(); const isLatestUserMsg = message.role === "user" && lastUserIdx === idx; return (
); })} {status === "submitted" && (
Thinking...
)}
)}
{status === "submitted" ? : }
); }; export default Chat;