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": [ {