fix: Remove "Refining Answer" popup (#4783)

* Clean up logic

* Remove dead code

* Remove "Refining Answer" prompt
This commit is contained in:
Raunak Bhagat
2025-05-29 12:55:38 -07:00
committed by GitHub
parent ef4d5dcec3
commit edc2892785
4 changed files with 2 additions and 527 deletions

View File

@@ -2881,10 +2881,6 @@ export function ChatPage({
selectedMessageForDocDisplay ==
secondLevelMessage?.messageId)
}
isImprovement={
message.isImprovement ||
nextMessage?.isImprovement
}
secondLevelGenerating={
(message.second_level_generating &&
currentSessionChatState !==
@@ -2912,21 +2908,6 @@ export function ChatPage({
agenticDocs={
message.agentic_docs || agenticDocs
}
toggleDocDisplay={(
agentic: boolean
) => {
if (agentic) {
setSelectedMessageForDocDisplay(
message.messageId
);
} else {
setSelectedMessageForDocDisplay(
secondLevelMessage
? secondLevelMessage.messageId
: null
);
}
}}
docs={
message?.documents &&
message?.documents.length > 0
@@ -2977,7 +2958,6 @@ export function ChatPage({
messageHistory.length - 1 == i ||
messageHistory.length - 2 == i
}
selectedDocuments={selectedDocuments}
toggleDocumentSelection={(
second: boolean
) => {

View File

@@ -1,422 +0,0 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import {
Tooltip,
TooltipTrigger,
TooltipProvider,
TooltipContent,
} from "@/components/ui/tooltip";
import { FiChevronRight } from "react-icons/fi";
import { StatusIndicator, ToggleState } from "./message/SubQuestionsDisplay";
import { SubQuestionDetail } from "./interfaces";
import {
PHASES_ORDER,
StreamingPhase,
StreamingPhaseText,
} from "./message/StreamingMessages";
import { Badge } from "@/components/ui/badge";
import next from "next";
export function useOrderedPhases(externalPhase: StreamingPhase) {
const [phaseQueue, setPhaseQueue] = useState<StreamingPhase[]>([]);
const [displayedPhases, setDisplayedPhases] = useState<StreamingPhase[]>([]);
const lastDisplayTimeRef = useRef<number>(Date.now());
const MIN_DELAY = 300; // 0.5 seconds
const getPhaseIndex = (phase: StreamingPhase) => PHASES_ORDER.indexOf(phase);
const finalPhaseIndex = useRef<number | null>(null);
// Whenever externalPhase changes, add any missing steps into the queue
useEffect(() => {
setPhaseQueue((prevQueue) => {
const lastIndex = finalPhaseIndex.current || 0;
let targetPhase = externalPhase;
let targetIndex = getPhaseIndex(targetPhase);
// If the new target is before or at the last displayed, do nothing
if (targetIndex <= lastIndex) {
return prevQueue;
}
finalPhaseIndex.current = targetIndex;
// Otherwise, collect all missing phases from lastDisplayed+1 up to targetIndex
const missingPhases: StreamingPhase[] = PHASES_ORDER.slice(
0,
targetIndex + 1
);
return [...prevQueue, ...missingPhases];
});
}, [externalPhase, displayedPhases]);
// Process the queue, displaying each queued phase for at least MIN_DELAY (0.5s)
useEffect(() => {
if (phaseQueue.length === 0) return;
let rafId: number;
const processQueue = () => {
const now = Date.now();
if (now - lastDisplayTimeRef.current >= MIN_DELAY) {
setPhaseQueue((prevQueue) => {
if (prevQueue.length > 0) {
const [nextPhase, ...rest] = prevQueue;
if (nextPhase !== undefined) {
setDisplayedPhases((prev) => [...prev, nextPhase]);
}
lastDisplayTimeRef.current = Date.now();
return rest;
}
return prevQueue;
});
}
rafId = requestAnimationFrame(processQueue);
};
rafId = requestAnimationFrame(processQueue);
return () => cancelAnimationFrame(rafId);
}, [phaseQueue]);
return displayedPhases;
}
export function RefinemenetBadge({
setToolTipHovered,
overallAnswer,
secondLevelSubquestions,
finished,
setCanShowResponse,
canShowResponse,
}: {
setToolTipHovered: (hovered: boolean) => void;
finished: boolean;
overallAnswer: string;
secondLevelSubquestions?: SubQuestionDetail[] | null;
setCanShowResponse: (canShowResponse: boolean) => void;
canShowResponse: boolean;
}) {
// Derive the 'externalPhase' from your existing logic:
const currentState =
overallAnswer.length > 0
? finished
? StreamingPhase.COMPLETE
: StreamingPhase.ANSWER
: secondLevelSubquestions?.[0]
? secondLevelSubquestions.every((q) => q.answer && q.answer.length > 0)
? StreamingPhase.EVALUATE
: secondLevelSubquestions?.[0].context_docs
? StreamingPhase.CONTEXT_DOCS
: secondLevelSubquestions?.[0].sub_queries
? StreamingPhase.SUB_QUERIES
: StreamingPhase.WAITING
: StreamingPhase.WAITING;
// Get the array of displayed phases
const displayedPhases = useOrderedPhases(currentState);
const isDone = displayedPhases.includes(StreamingPhase.COMPLETE);
// Expand/collapse, hover states
const [expanded] = useState(true);
const [toolTipHoveredInternal, setToolTipHoveredInternal] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const [shouldShow, setShouldShow] = useState(true);
// Refs for bounding area checks
const containerRef = useRef<HTMLDivElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
// Keep the tooltip open if hovered on container or tooltip
// Remove the old onMouseLeave calls and rely on bounding area checks
useEffect(() => {
function handleMouseMove(e: MouseEvent) {
if (!containerRef.current || !tooltipRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
const [x, y] = [e.clientX, e.clientY];
const inContainer =
x >= containerRect.left &&
x <= containerRect.right &&
y >= containerRect.top &&
y <= containerRect.bottom;
const inTooltip =
x >= tooltipRect.left &&
x <= tooltipRect.right &&
y >= tooltipRect.top &&
y <= tooltipRect.bottom;
// If not hovering in either region, close tooltip
if (!inContainer && !inTooltip) {
setToolTipHoveredInternal(false);
setToolTipHovered(false);
setIsHovered(false);
}
}
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [setToolTipHovered]);
// Once "done", hide after a short delay if not hovered
useEffect(() => {
if (isDone) {
const timer = setTimeout(() => {
setShouldShow(false);
setCanShowResponse(true);
}, 800);
return () => clearTimeout(timer);
}
}, [isDone, isHovered, setCanShowResponse]);
if (!shouldShow) {
return null; // entire box disappears
}
return (
<TooltipProvider delayDuration={0}>
{/*
IMPORTANT: We rely on open={ isHovered || toolTipHoveredInternal }
to keep the tooltip visible if either the badge or tooltip is hovered.
*/}
<Tooltip open={isHovered || toolTipHoveredInternal}>
<div
className="relative w-fit max-w-sm"
ref={containerRef}
// onMouseEnter keeps the tooltip open
onMouseEnter={() => {
setIsHovered(true);
setToolTipHoveredInternal(true);
setToolTipHovered(true);
}}
// Remove the explicit onMouseLeave the global bounding check will close it
>
<TooltipTrigger asChild>
<div className="flex items-center gap-x-1 text-black text-sm font-medium cursor-pointer hover:text-blue-600 transition-colors duration-200">
<p className="text-sm loading-text font-medium">
Refining Answer
</p>
<FiChevronRight
className={`inline-block my-auto transition-transform duration-200 text-text-darker ${
isHovered ? "rotate-90" : ""
}`}
size={16}
/>
</div>
</TooltipTrigger>
{expanded && (
<TooltipContent
ref={tooltipRef}
// onMouseEnter keeps the tooltip open when cursor enters tooltip
onMouseEnter={() => {
setToolTipHoveredInternal(true);
setToolTipHovered(true);
}}
// Remove onMouseLeave and rely on bounding box logic to close
side="bottom"
align="start"
width="w-fit"
className=" -mt-1 p-4 bg-[#fff] dark:bg-[#000] border-2 border-border dark:border-neutral-800 shadow-lg rounded-md"
>
{/* If not done, show the "Refining" box + a chevron */}
{/* Expanded area: each displayed phase in order */}
<div className="items-start flex flex-col gap-y-2">
{currentState !== StreamingPhase.WAITING ? (
Array.from(new Set(displayedPhases)).map((phase, index) => {
let status = ToggleState.Done;
if (
index ===
Array.from(new Set(displayedPhases)).length - 1 &&
phase !== StreamingPhase.COMPLETE
) {
status = ToggleState.InProgress;
}
return (
<div
key={phase}
className="text-text flex items-center justify-start gap-x-2"
>
<div className="w-3 h-3">
<StatusIndicator status={status} />
</div>
<span className="text-sm font-medium">
{StreamingPhaseText[phase]}
</span>
</div>
);
})
) : (
<div
key={currentState}
className="text-text flex items-center justify-start gap-x-2"
>
<div className="w-3 h-3">
<StatusIndicator status={ToggleState.InProgress} />
</div>
<span className="text-sm font-medium">
{StreamingPhaseText[StreamingPhase.SUB_QUERIES]}
</span>
</div>
)}
</div>
</TooltipContent>
)}
</div>
</Tooltip>
</TooltipProvider>
);
}
export const NoNewAnswerMessage = () => {
const [opacity, setOpacity] = useState(1);
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const fadeOutDuration = 2000; // 2 seconds
const intervalDuration = 50; // Update every 50ms for smooth fade
const opacityStep = intervalDuration / fadeOutDuration;
const fadeOutInterval = setInterval(() => {
setOpacity((prevOpacity) => {
const newOpacity = prevOpacity - opacityStep;
return newOpacity > 0 ? newOpacity : 0;
});
}, intervalDuration);
const timer = setTimeout(() => {
clearInterval(fadeOutInterval);
setIsVisible(false);
}, fadeOutDuration);
return () => {
clearInterval(fadeOutInterval);
clearTimeout(timer);
};
}, []);
if (!isVisible) return null;
return (
<div
className="text-text-600 text-sm transition-opacity duration-2000 ease-out"
style={{ opacity: opacity }}
>
No new answer found...
</div>
);
};
export function StatusRefinement({
canShowResponse,
setCanShowResponse,
isImprovement,
isViewingInitialAnswer,
toggleDocDisplay,
secondLevelSubquestions,
secondLevelAssistantMessage,
secondLevelGenerating,
subQuestions,
setIsViewingInitialAnswer,
noShowingMessage,
}: {
canShowResponse: boolean;
setCanShowResponse: (canShowResponse: boolean) => void;
isImprovement?: boolean | null;
isViewingInitialAnswer: boolean;
toggleDocDisplay: (isViewingInitialAnswer: boolean) => void;
secondLevelSubquestions: SubQuestionDetail[] | null;
secondLevelAssistantMessage: string;
secondLevelGenerating: boolean;
subQuestions: SubQuestionDetail[] | null;
setIsViewingInitialAnswer: (isViewingInitialAnswer: boolean) => void;
noShowingMessage?: boolean;
}) {
const [toolTipHovered, setToolTipHovered] = useState(false);
if (!secondLevelGenerating && isImprovement == undefined) {
return null;
}
if (noShowingMessage && !isImprovement) {
return <></>;
}
return (
<>
{(!canShowResponse || isImprovement == null) &&
subQuestions &&
subQuestions.length > 0 ? (
<RefinemenetBadge
setToolTipHovered={setToolTipHovered}
setCanShowResponse={setCanShowResponse}
canShowResponse={canShowResponse || false}
finished={!secondLevelGenerating}
overallAnswer={secondLevelAssistantMessage || ""}
secondLevelSubquestions={secondLevelSubquestions}
/>
) : secondLevelAssistantMessage ? (
isImprovement ? (
<TooltipProvider delayDuration={0}>
<Tooltip open={toolTipHovered}>
<TooltipTrigger
onMouseLeave={() => setToolTipHovered(false)}
asChild
>
<Badge
// NOTE: This is a hack to make the badge slightly higher
className="cursor-pointer mt-[1px]"
variant={`${
isViewingInitialAnswer ? "agent" : "agent-faded"
}`}
onClick={() => {
const viewInitialAnswer = !isViewingInitialAnswer;
setIsViewingInitialAnswer(viewInitialAnswer);
toggleDocDisplay && toggleDocDisplay(viewInitialAnswer);
}}
>
{isViewingInitialAnswer
? "See Refined Answer"
: "See Original Answer"}
</Badge>
</TooltipTrigger>
<TooltipContent
onMouseLeave={() => setToolTipHovered(false)}
side="bottom"
align="start"
width="w-fit"
className="w-fit p-4 bg-[#fff] border-2 border-border dark:border-neutral-800 shadow-lg rounded-md"
>
{/* If not done, show the "Refining" box + a chevron */}
{/* Expanded area: each displayed phase in order */}
<div className="items-start flex flex-col gap-y-2">
{PHASES_ORDER.map((phase) => (
<div
key={phase}
className="text-text flex items-center justify-start gap-x-2"
>
<div className="w-3 h-3">
<StatusIndicator status={ToggleState.Done} />
</div>
<span className="text-neutral-800 text-sm font-medium">
{StreamingPhaseText[phase]}
</span>
</div>
))}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<NoNewAnswerMessage />
)
) : (
<></>
)}
</>
);
}

View File

@@ -11,7 +11,7 @@ import React, {
useState,
} from "react";
import ReactMarkdown from "react-markdown";
import { OnyxDocument, FilteredOnyxDocument } from "@/lib/search/interfaces";
import { OnyxDocument } from "@/lib/search/interfaces";
import remarkGfm from "remark-gfm";
import { CopyButton } from "@/components/CopyButton";
import {
@@ -35,7 +35,6 @@ import {
CustomTooltip,
TooltipGroup,
} from "@/components/tooltip/CustomTooltip";
import { ValidSources } from "@/lib/types";
import { useMouseTracking } from "./hooks";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import RegenerateOption from "../RegenerateOption";
@@ -56,7 +55,6 @@ import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import SubQuestionsDisplay from "./SubQuestionsDisplay";
import { StatusRefinement } from "../Refinement";
import { copyAll, handleCopy } from "./copyingUtils";
import { ErrorBanner } from "./Resubmit";
import { transformLinkUri } from "@/lib/utils";
@@ -65,7 +63,6 @@ export const AgenticMessage = ({
isStreamingQuestions,
isGenerating,
docSidebarToggled,
isImprovement,
secondLevelAssistantMessage,
secondLevelGenerating,
regenerate,
@@ -79,7 +76,6 @@ export const AgenticMessage = ({
messageId,
content,
files,
selectedDocuments,
query,
citedDocuments,
toolCall,
@@ -92,7 +88,6 @@ export const AgenticMessage = ({
subQuestions,
agenticDocs,
secondLevelSubquestions,
toggleDocDisplay,
error,
resubmit,
}: {
@@ -100,7 +95,6 @@ export const AgenticMessage = ({
isStreamingQuestions: boolean;
isGenerating: boolean;
docSidebarToggled?: boolean;
isImprovement?: boolean | null;
secondLevelSubquestions?: SubQuestionDetail[] | null;
agenticDocs?: OnyxDocument[] | null;
secondLevelGenerating?: boolean;
@@ -111,7 +105,6 @@ export const AgenticMessage = ({
continueGenerating?: () => void;
otherMessagesCanSwitchTo?: number[];
onMessageSelection?: (messageId: number) => void;
selectedDocuments?: OnyxDocument[] | null;
toggleDocumentSelection?: (second: boolean) => void;
docs?: OnyxDocument[] | null;
alternativeAssistant?: Persona | null;
@@ -127,11 +120,8 @@ export const AgenticMessage = ({
overriddenModel?: string;
regenerate?: (modelOverRide: LlmDescriptor) => Promise<void>;
setPresentingDocument?: (document: OnyxDocument) => void;
toggleDocDisplay?: (agentic: boolean) => void;
error?: string | null;
}) => {
const [noShowingMessage, setNoShowingMessage] = useState(isComplete);
const [lastKnownContentLength, setLastKnownContentLength] = useState(0);
const [allowStreaming, setAllowStreaming] = useState(isComplete);
@@ -214,9 +204,8 @@ export const AgenticMessage = ({
}
}, [hasThinkingTokens]);
const [isViewingInitialAnswer, setIsViewingInitialAnswer] = useState(true);
const isViewingInitialAnswer = true;
const [canShowResponse, setCanShowResponse] = useState(isComplete);
const [isRegenerateDropdownVisible, setIsRegenerateDropdownVisible] =
useState(false);
@@ -224,8 +213,6 @@ export const AgenticMessage = ({
const settings = useContext(SettingsContext);
const selectedDocumentIds =
selectedDocuments?.map((document) => document.document_id) || [];
const citedDocumentIds: string[] = [];
citedDocuments?.forEach((doc) => {
@@ -250,27 +237,6 @@ export const AgenticMessage = ({
content = trimIncompleteCodeSection(content);
}
let filteredDocs: FilteredOnyxDocument[] = [];
if (docs) {
filteredDocs = docs
.filter(
(doc, index, self) =>
doc.document_id &&
doc.document_id !== "" &&
index === self.findIndex((d) => d.document_id === doc.document_id)
)
.filter((doc) => {
return citedDocumentIds.includes(doc.document_id);
})
.map((doc: OnyxDocument, ind: number) => {
return {
...doc,
included: selectedDocumentIds.includes(doc.document_id),
};
});
}
const paragraphCallback = useCallback(
(props: any, fontSize: "sm" | "base" = "base") => (
<MemoizedParagraph fontSize={fontSize}>
@@ -332,10 +298,6 @@ export const AgenticMessage = ({
? otherMessagesCanSwitchTo?.indexOf(messageId)
: undefined;
const uniqueSources: ValidSources[] = Array.from(
new Set((docs || []).map((doc) => doc.source_type))
).slice(0, 3);
const markdownComponents = useMemo(
() => ({
a: anchorCallback,
@@ -415,10 +377,8 @@ export const AgenticMessage = ({
if (typeof finalContent !== "string") return;
let currentIndex = streamedContent.length;
let intervalId: NodeJS.Timeout | null = null;
// if (finalContent.length > currentIndex) {
intervalId = setInterval(() => {
setStreamedContent((prev) => {
if (prev.length < finalContent.length) {
@@ -430,9 +390,6 @@ export const AgenticMessage = ({
}
});
}, 10);
// } else {
// setStreamedContent(finalContent);
// }
return () => {
if (intervalId) clearInterval(intervalId);
@@ -521,31 +478,6 @@ export const AgenticMessage = ({
<div className="text-black text-lg font-medium">
Answer
</div>
<StatusRefinement
noShowingMessage={noShowingMessage}
canShowResponse={canShowResponse || false}
setCanShowResponse={setCanShowResponse}
isImprovement={isImprovement}
isViewingInitialAnswer={isViewingInitialAnswer}
toggleDocDisplay={toggleDocDisplay!}
secondLevelSubquestions={
secondLevelSubquestions || []
}
secondLevelAssistantMessage={
secondLevelAssistantMessage || ""
}
secondLevelGenerating={
(secondLevelGenerating &&
finalContent.length ==
streamedContent.length) ||
false
}
subQuestions={subQuestions}
setIsViewingInitialAnswer={
setIsViewingInitialAnswer
}
/>
</div>
<div className="px-4">

View File

@@ -285,7 +285,6 @@ export function SharedChatDisplay({
isGenerating={false}
shared
key={message.messageId}
isImprovement={message.isImprovement}
secondLevelGenerating={false}
secondLevelSubquestions={message.sub_questions?.filter(
(subQuestion) => subQuestion.level === 1
@@ -302,19 +301,6 @@ export function SharedChatDisplay({
) || []
}
agenticDocs={message.agentic_docs || agenticDocs}
toggleDocDisplay={(agentic: boolean) => {
if (agentic) {
setSelectedMessageForDocDisplay(
message.messageId
);
} else {
setSelectedMessageForDocDisplay(
secondLevelMessage
? secondLevelMessage.messageId
: null
);
}
}}
docs={message?.documents}
setPresentingDocument={setPresentingDocument}
overriddenModel={message.overridden_model}
@@ -328,7 +314,6 @@ export function SharedChatDisplay({
)}
toolCall={message.toolCall}
isComplete={true}
selectedDocuments={[]}
toggleDocumentSelection={() => {
if (
!documentSidebarVisible ||