mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Co-authored-by: Chandrapal Badshah <12944530+Chan9390@users.noreply.github.com> Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com> Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com> Co-authored-by: Adrián Jesús Peña Rodríguez <adrianjpr@gmail.com> Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
125 lines
3.6 KiB
TypeScript
125 lines
3.6 KiB
TypeScript
/**
|
|
* MessageItem component
|
|
* Renders individual chat messages with actions for assistant messages
|
|
*/
|
|
|
|
import { Copy, RotateCcw } from "lucide-react";
|
|
import { Streamdown } from "streamdown";
|
|
|
|
import { Action, Actions } from "@/components/lighthouse/ai-elements/actions";
|
|
import { ChainOfThoughtDisplay } from "@/components/lighthouse/chain-of-thought-display";
|
|
import {
|
|
extractChainOfThoughtEvents,
|
|
extractMessageText,
|
|
type Message,
|
|
MESSAGE_ROLES,
|
|
MESSAGE_STATUS,
|
|
} from "@/components/lighthouse/chat-utils";
|
|
import { Loader } from "@/components/lighthouse/loader";
|
|
|
|
interface MessageItemProps {
|
|
message: Message;
|
|
index: number;
|
|
isLastMessage: boolean;
|
|
status: string;
|
|
onCopy: (text: string) => void;
|
|
onRegenerate: () => void;
|
|
}
|
|
|
|
export function MessageItem({
|
|
message,
|
|
index,
|
|
isLastMessage,
|
|
status,
|
|
onCopy,
|
|
onRegenerate,
|
|
}: MessageItemProps) {
|
|
const messageText = extractMessageText(message);
|
|
|
|
// Check if this is the streaming assistant message
|
|
const isStreamingAssistant =
|
|
isLastMessage &&
|
|
message.role === MESSAGE_ROLES.ASSISTANT &&
|
|
status === MESSAGE_STATUS.STREAMING;
|
|
|
|
// Use a composite key to ensure uniqueness even if IDs are duplicated temporarily
|
|
const uniqueKey = `${message.id}-${index}-${message.role}`;
|
|
|
|
// Extract chain-of-thought events from message parts
|
|
const chainOfThoughtEvents = extractChainOfThoughtEvents(message);
|
|
|
|
return (
|
|
<div key={uniqueKey}>
|
|
<div
|
|
className={`flex ${
|
|
message.role === MESSAGE_ROLES.USER ? "justify-end" : "justify-start"
|
|
}`}
|
|
>
|
|
<div
|
|
className={`max-w-[80%] rounded-lg px-4 py-2 ${
|
|
message.role === MESSAGE_ROLES.USER
|
|
? "bg-bg-neutral-tertiary border-border-neutral-secondary border"
|
|
: "bg-muted"
|
|
}`}
|
|
>
|
|
{/* Chain of Thought for assistant messages */}
|
|
{message.role === MESSAGE_ROLES.ASSISTANT && (
|
|
<ChainOfThoughtDisplay
|
|
events={chainOfThoughtEvents}
|
|
isStreaming={isStreamingAssistant}
|
|
messageKey={uniqueKey}
|
|
/>
|
|
)}
|
|
|
|
{/* Show loader only if streaming with no text AND no chain-of-thought events */}
|
|
{isStreamingAssistant &&
|
|
!messageText &&
|
|
chainOfThoughtEvents.length === 0 ? (
|
|
<Loader size="default" text="Thinking..." />
|
|
) : messageText ? (
|
|
<div>
|
|
<Streamdown
|
|
parseIncompleteMarkdown={true}
|
|
shikiTheme={["github-light", "github-dark"]}
|
|
controls={{
|
|
code: true,
|
|
table: true,
|
|
mermaid: true,
|
|
}}
|
|
isAnimating={isStreamingAssistant}
|
|
>
|
|
{messageText}
|
|
</Streamdown>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions for assistant messages */}
|
|
{message.role === MESSAGE_ROLES.ASSISTANT &&
|
|
isLastMessage &&
|
|
messageText &&
|
|
status !== MESSAGE_STATUS.STREAMING && (
|
|
<div className="mt-2 flex justify-start">
|
|
<Actions className="max-w-[80%]">
|
|
<Action
|
|
tooltip="Copy message"
|
|
label="Copy"
|
|
onClick={() => onCopy(messageText)}
|
|
>
|
|
<Copy className="h-3 w-3" />
|
|
</Action>
|
|
<Action
|
|
tooltip="Regenerate response"
|
|
label="Retry"
|
|
onClick={onRegenerate}
|
|
>
|
|
<RotateCcw className="h-3 w-3" />
|
|
</Action>
|
|
</Actions>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|