fix(lighthouse): Display errors in Lighthouse & allow resending message (#8400)

Co-authored-by: Chandrapal Badshah <Chan9390@users.noreply.github.com>
Co-authored-by: Pablo Lara <larabjj@gmail.com>
This commit is contained in:
Prowler Bot
2025-07-30 12:42:49 +02:00
committed by GitHub
parent 15e4107065
commit 98d27ecfa0
3 changed files with 151 additions and 24 deletions
+15
View File
@@ -2,6 +2,21 @@
All notable changes to the **Prowler UI** are documented in this file.
## [1.10.0] (Prowler v5.10.0 - UNRELEASED)
### Added
- Lighthouse banner [(#8259)](https://github.com/prowler-cloud/prowler/pull/8259)
- Integration with Amazon S3, enabling storage and retrieval of scan data via S3 buckets [(#8056)](https://github.com/prowler-cloud/prowler/pull/8056)
___
## [v1.9.3] (Prowler v5.9.3)
### 🐞 Fixed
- Display error messages and allow editing last message in Lighthouse [(#8358)](https://github.com/prowler-cloud/prowler/pull/8358)
## [v1.9.0] (Prowler v5.9.0)
### 🚀 Added
+25 -8
View File
@@ -1,6 +1,7 @@
import { LangChainAdapter, Message } from "ai";
import { getLighthouseConfig } from "@/actions/lighthouse/lighthouse";
import { getErrorMessage } from "@/lib/helper";
import { getCurrentDataSection } from "@/lib/lighthouse/data";
import {
convertLangChainMessageToVercelMessage,
@@ -73,22 +74,38 @@ export async function POST(req: Request) {
const stream = new ReadableStream({
async start(controller) {
for await (const { event, data, tags } of agentStream) {
if (event === "on_chat_model_stream") {
if (data.chunk.content && !!tags && tags.includes("supervisor")) {
const chunk = data.chunk;
const aiMessage = convertLangChainMessageToVercelMessage(chunk);
controller.enqueue(aiMessage);
try {
for await (const { event, data, tags } of agentStream) {
if (event === "on_chat_model_stream") {
if (data.chunk.content && !!tags && tags.includes("supervisor")) {
const chunk = data.chunk;
const aiMessage = convertLangChainMessageToVercelMessage(chunk);
controller.enqueue(aiMessage);
}
}
}
controller.close();
} catch (error) {
const errorName =
error instanceof Error ? error.constructor.name : "UnknownError";
const errorMessage =
error instanceof Error ? error.message : String(error);
controller.enqueue({
id: "error-" + Date.now(),
role: "assistant",
content: `[LIGHTHOUSE_ANALYST_ERROR]: ${errorName}: ${errorMessage}`,
});
controller.close();
}
controller.close();
},
});
return LangChainAdapter.toDataStreamResponse(stream);
} catch (error) {
console.error("Error in POST request:", error);
return Response.json({ error: "An error occurred" }, { status: 500 });
return Response.json(
{ error: await getErrorMessage(error) },
{ status: 500 },
);
}
}
+111 -16
View File
@@ -2,7 +2,7 @@
import { useChat } from "@ai-sdk/react";
import Link from "next/link";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { MemoizedMarkdown } from "@/components/lighthouse/memoized-markdown";
@@ -25,20 +25,43 @@ interface ChatFormData {
}
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 [errorMessage, setErrorMessage] = useState<string | null>(null);
const {
messages,
handleSubmit,
handleInputChange,
append,
status,
error,
setMessages,
} = useChat({
api: "/api/lighthouse/analyst",
credentials: "same-origin",
experimental_throttle: 100,
sendExtraMessageFields: true,
onFinish: (message) => {
// Detect error messages sent from backend using specific prefix
if (message.content?.startsWith("[LIGHTHOUSE_ANALYST_ERROR]:")) {
const errorText = message.content
.replace("[LIGHTHOUSE_ANALYST_ERROR]:", "")
.trim();
setErrorMessage(errorText);
// Remove error message from chat history
setMessages((prev) =>
prev.filter(
(m) => !m.content?.startsWith("[LIGHTHOUSE_ANALYST_ERROR]:"),
),
);
}
},
);
onError: (error) => {
console.error("Chat error:", error);
setErrorMessage(
error?.message || "An error occurred. Please retry your message.",
);
},
});
const form = useForm<ChatFormData>({
defaultValues: {
@@ -65,8 +88,29 @@ export const Chat = ({ hasConfig, isActive }: ChatProps) => {
}
}, [status, form]);
// Populate input with last user message when any error occurs
useEffect(() => {
if (errorMessage && messages.length > 0) {
// Filter out the error message itself before finding the last user message
const nonErrorMessages = messages.filter(
(m) => !m.content?.startsWith("[LIGHTHOUSE_ANALYST_ERROR]:"),
);
if (nonErrorMessages.length > 0) {
const lastUserMessage = nonErrorMessages
.filter((m) => m.role === "user")
.pop();
if (lastUserMessage && !messageValue) {
form.setValue("message", lastUserMessage.content);
setMessages(nonErrorMessages.slice(0, -1));
}
}
}
}, [errorMessage, messages, messageValue, form, setMessages]);
const onFormSubmit = form.handleSubmit((data) => {
if (data.message.trim()) {
// Clear error on new submission
setErrorMessage(null);
handleSubmit();
}
});
@@ -148,7 +192,54 @@ export const Chat = ({ hasConfig, isActive }: ChatProps) => {
</div>
)}
{messages.length === 0 ? (
{/* Error Banner */}
{(error || errorMessage) && (
<div className="mx-4 mt-4 rounded-lg border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20">
<div className="flex items-start">
<div className="flex-shrink-0">
<svg
className="h-5 w-5 text-red-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800 dark:text-red-200">
Error
</h3>
<p className="mt-1 text-sm text-red-700 dark:text-red-300">
{errorMessage ||
error?.message ||
"An error occurred. Please retry your message."}
</p>
{/* Original error details for native errors */}
{error && (error as any).status && (
<p className="mt-1 text-xs text-red-600 dark:text-red-400">
Status: {(error as any).status}
</p>
)}
{error && (error as any).body && (
<details className="mt-2">
<summary className="cursor-pointer text-xs text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300">
Show details
</summary>
<pre className="mt-1 max-h-20 overflow-auto rounded bg-red-100 p-2 text-xs text-red-800 dark:bg-red-900/30 dark:text-red-200">
{JSON.stringify((error as any).body, null, 2)}
</pre>
</details>
)}
</div>
</div>
</div>
)}
{messages.length === 0 && !errorMessage && !error ? (
<div className="flex flex-1 items-center justify-center p-4">
<div className="w-full max-w-2xl">
<h2 className="mb-4 text-center font-sans text-xl">Suggestions</h2>
@@ -232,7 +323,11 @@ export const Chat = ({ hasConfig, isActive }: ChatProps) => {
control={form.control}
name="message"
label=""
placeholder="Type your message..."
placeholder={
error || errorMessage
? "Edit your message and try again..."
: "Type your message..."
}
variant="bordered"
minRows={1}
maxRows={6}