diff --git a/frontend/app/chat/_components/assistant-message.tsx b/frontend/app/chat/_components/assistant-message.tsx
index c4ac16e81..02ba2e061 100644
--- a/frontend/app/chat/_components/assistant-message.tsx
+++ b/frontend/app/chat/_components/assistant-message.tsx
@@ -11,6 +11,7 @@ const MarkdownRenderer = dynamic(
{ ssr: false },
);
+import { trackButton } from "@/lib/analytics";
import { cn } from "@/lib/utils";
import type {
FunctionCall,
@@ -18,6 +19,7 @@ import type {
} from "../_types/types";
import { FunctionCalls } from "./function-calls";
import { Message } from "./message";
+import MessageActions from "./message-actions";
import { TokenUsage } from "./token-usage";
interface AssistantMessageProps {
@@ -35,6 +37,8 @@ interface AssistantMessageProps {
delay?: number;
isInitialGreeting?: boolean;
usage?: TokenUsageType;
+ timestamp?: Date;
+ showFeedback?: boolean;
}
export function AssistantMessage({
@@ -52,7 +56,19 @@ export function AssistantMessage({
delay = 0.2,
isInitialGreeting = false,
usage,
+ timestamp,
+ showFeedback = true,
}: AssistantMessageProps) {
+ const trackFeedback = (feedback: "like" | "dislike") => {
+ trackButton({
+ action: feedback,
+ elementId: "message-feedback",
+ namespace: "chat",
+ CTA: feedback === "like" ? "Like Message" : "Dislike Message",
+ timestamp: timestamp?.getTime(),
+ });
+ };
+
return (
{usage && !isStreaming && }
+ {!isInitialGreeting && showFeedback && !isStreaming && (
+
+ )}
diff --git a/frontend/app/chat/_components/message-actions.tsx b/frontend/app/chat/_components/message-actions.tsx
new file mode 100644
index 000000000..35bd41ce0
--- /dev/null
+++ b/frontend/app/chat/_components/message-actions.tsx
@@ -0,0 +1,58 @@
+import { ThumbsDown, ThumbsUp } from "lucide-react";
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+
+interface MessageActionsProps {
+ trackFeedback: (feedback: "like" | "dislike") => void;
+}
+
+const MessageActions = ({ trackFeedback }: MessageActionsProps) => {
+ const [feedbackSelected, setFeedbackSelected] = useState<
+ "like" | "dislike" | null
+ >(null);
+
+ const handleFeedback = (feedback: "like" | "dislike") => {
+ if (feedbackSelected === feedback) return; // Prevent multiple tracking events for the same feedback
+ trackFeedback(feedback);
+ setFeedbackSelected(feedback);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+export default MessageActions;
diff --git a/frontend/app/chat/_types/types.ts b/frontend/app/chat/_types/types.ts
index cd39daba3..fde98c543 100644
--- a/frontend/app/chat/_types/types.ts
+++ b/frontend/app/chat/_types/types.ts
@@ -21,6 +21,12 @@ export interface Message {
usage?: TokenUsage;
}
+export const INITIAL_ASSISTANT_MESSAGE: Message = {
+ role: "assistant",
+ content: "How can I assist?",
+ timestamp: new Date(),
+};
+
export interface FunctionCall {
name: string;
arguments?: Record;
diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx
index 524cc991d..49171dda0 100644
--- a/frontend/app/chat/page.tsx
+++ b/frontend/app/chat/page.tsx
@@ -29,6 +29,7 @@ import type {
RequestBody,
ToolCallResult,
} from "./_types/types";
+import { INITIAL_ASSISTANT_MESSAGE } from "./_types/types";
function ChatPage() {
const isDebugMode = process.env.NEXT_PUBLIC_OPENRAG_DEBUG === "true";
@@ -52,11 +53,7 @@ function ChatPage() {
setConversationFilter,
} = useChat();
const [messages, setMessages] = useState([
- {
- role: "assistant",
- content: "How can I assist?",
- timestamp: new Date(),
- },
+ INITIAL_ASSISTANT_MESSAGE,
]);
const [input, setInput] = useState("");
const { loading, setLoading } = useLoadingStore();
@@ -328,13 +325,7 @@ function ChatPage() {
// Abort any in-flight streaming so it doesn't bleed into new chat
abortStream();
// Reset chat UI even if context state was already 'new'
- setMessages([
- {
- role: "assistant",
- content: "How can I assist?",
- timestamp: new Date(),
- },
- ]);
+ setMessages([INITIAL_ASSISTANT_MESSAGE]);
setInput("");
setExpandedFunctionCalls(new Set());
setIsFilterHighlighted(false);
@@ -572,13 +563,7 @@ function ChatPage() {
useEffect(() => {
if (placeholderConversation && currentConversationId === null) {
console.log("Starting new conversation");
- setMessages([
- {
- role: "assistant",
- content: "How can I assist?",
- timestamp: new Date(),
- },
- ]);
+ setMessages([INITIAL_ASSISTANT_MESSAGE]);
lastLoadedConversationRef.current = null;
// Focus input when starting a new conversation
@@ -1142,9 +1127,11 @@ function ChatPage() {
isInitialGreeting={
index === 0 &&
messages.length === 1 &&
- message.content === "How can I assist?"
+ message.content ===
+ INITIAL_ASSISTANT_MESSAGE.content
}
usage={message.usage}
+ timestamp={message.timestamp}
/>
)}
diff --git a/frontend/app/onboarding/_components/onboarding-content.tsx b/frontend/app/onboarding/_components/onboarding-content.tsx
index 7ad47428d..4a4b7cf3c 100644
--- a/frontend/app/onboarding/_components/onboarding-content.tsx
+++ b/frontend/app/onboarding/_components/onboarding-content.tsx
@@ -287,6 +287,7 @@ export function OnboardingContent({
onToggle={() => {}}
isStreaming={!!streamingMessage}
isCompleted={currentStep > 3}
+ showFeedback={false}
/>
)}
diff --git a/frontend/contexts/chat-context.tsx b/frontend/contexts/chat-context.tsx
index 10147a3db..b9584d823 100644
--- a/frontend/contexts/chat-context.tsx
+++ b/frontend/contexts/chat-context.tsx
@@ -11,6 +11,7 @@ import {
useState,
} from "react";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
+import { INITIAL_ASSISTANT_MESSAGE } from "@/app/chat/_types/types";
export type EndpointType = "chat" | "langflow";
@@ -343,7 +344,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
messages: [
{
role: "assistant",
- content: "How can I assist?",
+ content: INITIAL_ASSISTANT_MESSAGE.content,
timestamp: new Date().toISOString(),
},
],
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 705f5ce5e..4b73c4bcc 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -11,7 +11,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "react-jsx",
+ "jsx": "preserve",
"incremental": true,
"plugins": [
{