mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-03-26 17:51:54 +01:00
updated + functional
This commit is contained in:
parent
d32d1c6079
commit
28ba01b361
@ -250,25 +250,21 @@ export const constructSubQuestions = (
|
||||
console.log("STOP REASON");
|
||||
console.log(newDetail);
|
||||
const { level, level_question_nr } = newDetail;
|
||||
const actual_level_question_nr = level_question_nr ?? 0;
|
||||
let subQuestion = updatedSubQuestions.find(
|
||||
(sq) =>
|
||||
sq.level === level && sq.level_question_nr === actual_level_question_nr
|
||||
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
|
||||
);
|
||||
if (subQuestion) {
|
||||
subQuestion.is_complete = true;
|
||||
}
|
||||
} else if ("top_documents" in newDetail) {
|
||||
const { level, level_question_nr, top_documents } = newDetail;
|
||||
const actual_level_question_nr = level_question_nr ?? 0;
|
||||
let subQuestion = updatedSubQuestions.find(
|
||||
(sq) =>
|
||||
sq.level === level && sq.level_question_nr === actual_level_question_nr
|
||||
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
|
||||
);
|
||||
if (!subQuestion) {
|
||||
subQuestion = {
|
||||
level: level ?? 0,
|
||||
level_question_nr: actual_level_question_nr,
|
||||
level_question_nr: level_question_nr ?? 0,
|
||||
question: "",
|
||||
answer: "",
|
||||
sub_queries: [],
|
||||
@ -280,17 +276,15 @@ export const constructSubQuestions = (
|
||||
} else if ("answer_piece" in newDetail) {
|
||||
// Handle AgentAnswerPiece
|
||||
const { level, level_question_nr, answer_piece } = newDetail;
|
||||
const actual_level_question_nr = level_question_nr;
|
||||
// Find or create the relevant SubQuestionDetail
|
||||
let subQuestion = updatedSubQuestions.find(
|
||||
(sq) =>
|
||||
sq.level === level && sq.level_question_nr === actual_level_question_nr
|
||||
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
|
||||
);
|
||||
|
||||
if (!subQuestion) {
|
||||
subQuestion = {
|
||||
level,
|
||||
level_question_nr: actual_level_question_nr,
|
||||
level_question_nr,
|
||||
question: "",
|
||||
answer: "",
|
||||
sub_queries: [],
|
||||
@ -327,19 +321,17 @@ export const constructSubQuestions = (
|
||||
} else if ("sub_query" in newDetail) {
|
||||
// Handle SubQueryPiece
|
||||
const { level, level_question_nr, query_id, sub_query } = newDetail;
|
||||
const actual_level_question_nr = level_question_nr;
|
||||
|
||||
// Find the relevant SubQuestionDetail
|
||||
let subQuestion = updatedSubQuestions.find(
|
||||
(sq) =>
|
||||
sq.level === level && sq.level_question_nr === actual_level_question_nr
|
||||
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
|
||||
);
|
||||
|
||||
if (!subQuestion) {
|
||||
// If we receive a sub_query before its parent question, create a placeholder
|
||||
subQuestion = {
|
||||
level,
|
||||
level_question_nr: actual_level_question_nr,
|
||||
level_question_nr: level_question_nr,
|
||||
question: "",
|
||||
answer: "",
|
||||
sub_queries: [],
|
||||
|
@ -67,18 +67,9 @@ import remarkMath from "remark-math";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
import "katex/dist/katex.min.css";
|
||||
import SubQuestionsDisplay from "./SubQuestionsDisplay";
|
||||
import SubQuestionProgress from "./SubQuestionProgress";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { LoadingIndicator } from "react-select/dist/declarations/src/components/indicators";
|
||||
import {
|
||||
StreamingPhase,
|
||||
StreamingPhaseText,
|
||||
useStreamingMessages,
|
||||
} from "./StreamingMessages";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import RefinemenetBadge from "../refinmentBadge";
|
||||
import SubQuestionProgress from "./SubQuestionProgress";
|
||||
|
||||
export const AgenticMessage = ({
|
||||
secondLevelAssistantMessage,
|
||||
@ -466,7 +457,7 @@ export const AgenticMessage = ({
|
||||
|
||||
{/* For debugging purposes */}
|
||||
{/* <SubQuestionProgress subQuestions={subQuestions || []} /> */}
|
||||
|
||||
{/* */}
|
||||
{(allowStreaming &&
|
||||
finalContent &&
|
||||
finalContent.length > 8) ||
|
||||
@ -479,8 +470,13 @@ export const AgenticMessage = ({
|
||||
Answer
|
||||
</div>
|
||||
|
||||
{true ? (
|
||||
{!secondLevelAssistantMessage &&
|
||||
// !isGenerating &&
|
||||
subQuestions &&
|
||||
subQuestions.length > 0 ? (
|
||||
<RefinemenetBadge
|
||||
finished={!secondLevelGenerating}
|
||||
overallAnswer={secondLevelAssistantMessage || ""}
|
||||
secondLevelSubquestions={secondLevelSubquestions}
|
||||
toggleInitialAnswerVieinwg={() => {
|
||||
setIsViewingInitialAnswer(
|
||||
|
@ -25,6 +25,41 @@ import { CheckIcon, ChevronDown } from "lucide-react";
|
||||
import { PHASE_MIN_MS, useStreamingMessages } from "./StreamingMessages";
|
||||
import { CirclingArrowIcon } from "@/components/icons/icons";
|
||||
|
||||
export const StatusIndicator = ({ status }: { status: ToggleState }) => {
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
{status != ToggleState.InProgress ? (
|
||||
<div
|
||||
className={` h-full w-full rounded-full z-10
|
||||
bg-background border-3 border-background-900 "
|
||||
${
|
||||
status === ToggleState.Todo
|
||||
? "!border-4 border border-background-900 bg-background"
|
||||
: false
|
||||
? "bg-background border-3 border border-background-900 rotating-border"
|
||||
: "bg-background-900 flex items-center justify-center"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{status === ToggleState.Done && (
|
||||
<CheckIcon className="m-auto text-white" size={8} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-full w-full">
|
||||
<div className="absolute top-0 left-0 z-[100] h-full w-full rounded-full bg-background" />
|
||||
|
||||
<CirclingArrowIcon
|
||||
size={12}
|
||||
className="absolute top-0 left-0 z-[2000] h-full w-full animate-spin"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface TemporaryDisplay {
|
||||
question: string;
|
||||
tinyQuestion: string;
|
||||
@ -43,7 +78,7 @@ interface SubQuestionsDisplayProps {
|
||||
overallAnswerGenerating?: boolean;
|
||||
}
|
||||
|
||||
enum ToggleState {
|
||||
export enum ToggleState {
|
||||
Todo,
|
||||
InProgress,
|
||||
Done,
|
||||
@ -300,34 +335,9 @@ const SubQuestionDisplay: React.FC<{
|
||||
ref={questionRef}
|
||||
className={`flex items-start ${!isLast ? "pb-2" : ""}`}
|
||||
>
|
||||
{status != ToggleState.InProgress ? (
|
||||
<div
|
||||
className={`absolute left-0 w-3 h-3 rounded-full mt-[12px] z-10
|
||||
bg-background border-3 border-background-900 "
|
||||
${
|
||||
status === ToggleState.Todo
|
||||
? "!border-4 border border-background-900 bg-background"
|
||||
: false
|
||||
? "bg-background border-3 border border-background-900 rotating-border"
|
||||
: "bg-background-900 flex items-center justify-center"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{status === ToggleState.Done && (
|
||||
<CheckIcon className="m-auto text-white" size={8} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="absolute z-[100] left-0 mt-[12px] w-3 h-3 rounded-full bg-background" />
|
||||
|
||||
<CirclingArrowIcon
|
||||
size={12}
|
||||
className="absolute z-[2000] left-0 mt-[12px] w-3 h-3 animate-spin"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="absolute left-0 w-3 h-3 rounded-full mt-[12px] z-10">
|
||||
<StatusIndicator status={status} />
|
||||
</div>
|
||||
<div className="ml-8 w-full">
|
||||
<div
|
||||
className="flex -mx-2 rounded-md px-2 hover:bg-[#F5F3ED] items-start py-1.5 my-.5 cursor-pointer"
|
||||
|
@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
@ -6,69 +7,109 @@ import {
|
||||
TooltipContent,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { FiChevronRight, FiGlobe } from "react-icons/fi";
|
||||
import {
|
||||
StreamingPhase,
|
||||
StreamingPhaseText,
|
||||
} from "./message/StreamingMessages";
|
||||
import { SubQuestionDetail } from "./interfaces";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FiChevronRight, FiChevronDown, FiGlobe } from "react-icons/fi";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { CirclingArrowIcon } from "@/components/icons/icons";
|
||||
import { StatusIndicator, ToggleState } from "./message/SubQuestionsDisplay";
|
||||
|
||||
// Replace with your actual icons/components
|
||||
// import { CheckIcon, CirclingArrowIcon } from "@/your-icons-or-components";
|
||||
|
||||
/** --------------------------------------------------------------------------------
|
||||
* 1. StreamingPhase + Display Text
|
||||
* We add a pseudo "COMPARE" phase before COMPLETE to show "Comparing results"
|
||||
* for at least 0.5 seconds.
|
||||
* -------------------------------------------------------------------------------- */
|
||||
export enum StreamingPhase {
|
||||
WAITING = "WAITING",
|
||||
SUB_QUERIES = "SUB_QUERIES",
|
||||
CONTEXT_DOCS = "CONTEXT_DOCS",
|
||||
ANSWER = "ANSWER",
|
||||
COMPARE = "COMPARE",
|
||||
COMPLETE = "COMPLETE",
|
||||
EVALUATE = "EVALUATE",
|
||||
}
|
||||
|
||||
export const StreamingPhaseText: Record<StreamingPhase, string> = {
|
||||
[StreamingPhase.WAITING]: "Extracting key concepts",
|
||||
[StreamingPhase.SUB_QUERIES]: "Identifying additional questions",
|
||||
[StreamingPhase.CONTEXT_DOCS]: "Reading more documents",
|
||||
[StreamingPhase.ANSWER]: "Generating refined answer",
|
||||
[StreamingPhase.EVALUATE]: "Evaluating new context",
|
||||
[StreamingPhase.COMPARE]: "Comparing results",
|
||||
[StreamingPhase.COMPLETE]: "Finished",
|
||||
};
|
||||
|
||||
/** --------------------------------------------------------------------------------
|
||||
* 2. Ordered Phases: Ensure the COMPARE phase is inserted before COMPLETE
|
||||
* -------------------------------------------------------------------------------- */
|
||||
const PHASES_ORDER: StreamingPhase[] = [
|
||||
StreamingPhase.WAITING,
|
||||
StreamingPhase.SUB_QUERIES,
|
||||
StreamingPhase.CONTEXT_DOCS,
|
||||
StreamingPhase.ANSWER,
|
||||
StreamingPhase.COMPARE,
|
||||
StreamingPhase.COMPLETE,
|
||||
];
|
||||
|
||||
/** --------------------------------------------------------------------------------
|
||||
* 3. Hook to queue up phases in order, each with a minimum visible time of 0.5s
|
||||
* -------------------------------------------------------------------------------- */
|
||||
export function useOrderedPhases(externalPhase: StreamingPhase) {
|
||||
const [phaseQueue, setPhaseQueue] = useState<StreamingPhase[]>([]);
|
||||
const [displayedPhase, setDisplayedPhase] = useState<StreamingPhase>(
|
||||
StreamingPhase.WAITING
|
||||
);
|
||||
const lastDisplayTimestampRef = useRef<number>(Date.now());
|
||||
const [displayedPhases, setDisplayedPhases] = useState<StreamingPhase[]>([]);
|
||||
const lastDisplayTimeRef = useRef<number>(Date.now());
|
||||
const MIN_DELAY = 1000; // 0.5 seconds
|
||||
|
||||
const getPhaseIndex = (phase: StreamingPhase) => {
|
||||
return PHASES_ORDER.indexOf(phase);
|
||||
};
|
||||
const getPhaseIndex = (phase: StreamingPhase) => PHASES_ORDER.indexOf(phase);
|
||||
|
||||
// Whenever externalPhase changes, add any missing steps into the queue
|
||||
useEffect(() => {
|
||||
setPhaseQueue((prevQueue) => {
|
||||
const lastQueuedPhase =
|
||||
prevQueue.length > 0 ? prevQueue[prevQueue.length - 1] : displayedPhase;
|
||||
const lastDisplayed = displayedPhases[displayedPhases.length - 1];
|
||||
const lastIndex = lastDisplayed
|
||||
? getPhaseIndex(lastDisplayed)
|
||||
: getPhaseIndex(StreamingPhase.WAITING);
|
||||
|
||||
const lastQueuedIndex = getPhaseIndex(lastQueuedPhase);
|
||||
const externalIndex = getPhaseIndex(externalPhase);
|
||||
let targetPhase = externalPhase;
|
||||
let targetIndex = getPhaseIndex(targetPhase);
|
||||
|
||||
if (externalIndex <= lastQueuedIndex) {
|
||||
// If externalPhase is COMPLETE, show "COMPARE" first (unless we've shown it already)
|
||||
if (externalPhase === StreamingPhase.COMPLETE) {
|
||||
if (!displayedPhases.includes(StreamingPhase.COMPARE)) {
|
||||
targetPhase = StreamingPhase.COMPARE;
|
||||
targetIndex = getPhaseIndex(targetPhase);
|
||||
}
|
||||
}
|
||||
|
||||
// If the new target is before or at the last displayed, do nothing
|
||||
if (targetIndex <= lastIndex) {
|
||||
return prevQueue;
|
||||
}
|
||||
|
||||
// Otherwise, collect all missing phases from lastDisplayed+1 up to targetIndex
|
||||
const missingPhases: StreamingPhase[] = [];
|
||||
for (let i = lastQueuedIndex + 1; i <= externalIndex; i++) {
|
||||
for (let i = lastIndex + 1; i <= targetIndex; i++) {
|
||||
missingPhases.push(PHASES_ORDER[i]);
|
||||
}
|
||||
return [...prevQueue, ...missingPhases];
|
||||
});
|
||||
}, [externalPhase, displayedPhase]);
|
||||
}, [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;
|
||||
|
||||
let rafId: number;
|
||||
const processQueue = () => {
|
||||
const now = Date.now();
|
||||
const elapsed = now - lastDisplayTimestampRef.current;
|
||||
|
||||
// Keep this at 1000ms from the original example (unchanged),
|
||||
// but you can adjust if you want a different visible time in *this* component.
|
||||
if (elapsed >= 1000) {
|
||||
if (now - lastDisplayTimeRef.current >= MIN_DELAY) {
|
||||
setPhaseQueue((prevQueue) => {
|
||||
if (prevQueue.length > 0) {
|
||||
const [next, ...rest] = prevQueue;
|
||||
setDisplayedPhase(next);
|
||||
lastDisplayTimestampRef.current = Date.now();
|
||||
const [nextPhase, ...rest] = prevQueue;
|
||||
setDisplayedPhases((prev) => [...prev, nextPhase]);
|
||||
lastDisplayTimeRef.current = Date.now();
|
||||
return rest;
|
||||
}
|
||||
return prevQueue;
|
||||
@ -78,75 +119,348 @@ export function useOrderedPhases(externalPhase: StreamingPhase) {
|
||||
};
|
||||
|
||||
rafId = requestAnimationFrame(processQueue);
|
||||
return () => {
|
||||
if (rafId) {
|
||||
cancelAnimationFrame(rafId);
|
||||
}
|
||||
};
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
}, [phaseQueue]);
|
||||
|
||||
return StreamingPhaseText[displayedPhase];
|
||||
// displayedPhases are the ones currently shown, in the order they appeared
|
||||
return displayedPhases;
|
||||
}
|
||||
|
||||
/** --------------------------------------------------------------------------------
|
||||
* 4. StatusIndicator: shows "running" (spinner) or "finished" (check)
|
||||
* -------------------------------------------------------------------------------- */
|
||||
|
||||
/** --------------------------------------------------------------------------------
|
||||
* 5. Example SubQuestionDetail interface (from your snippet)
|
||||
* -------------------------------------------------------------------------------- */
|
||||
export interface SubQuestionDetail {
|
||||
question: string;
|
||||
answer: string;
|
||||
sub_queries?: { query: string }[] | null;
|
||||
context_docs?: { top_documents: any[] } | null;
|
||||
is_complete?: boolean;
|
||||
}
|
||||
|
||||
/** --------------------------------------------------------------------------------
|
||||
* 6. Final "RefinemenetBadge" component
|
||||
* - Renders a "Refining" box with phases shown in order
|
||||
* - Disappears once COMPLETE is reached, unless hovered
|
||||
* - Preserves any states already shown
|
||||
* -------------------------------------------------------------------------------- */
|
||||
export default function RefinemenetBadge({
|
||||
overallAnswer,
|
||||
secondLevelSubquestions,
|
||||
toggleInitialAnswerVieinwg,
|
||||
isViewingInitialAnswer,
|
||||
finished,
|
||||
}: {
|
||||
finished: boolean;
|
||||
overallAnswer: string;
|
||||
secondLevelSubquestions?: SubQuestionDetail[] | null;
|
||||
toggleInitialAnswerVieinwg: () => void;
|
||||
isViewingInitialAnswer: boolean;
|
||||
}) {
|
||||
const currentState = secondLevelSubquestions?.[0]
|
||||
? secondLevelSubquestions[0].answer
|
||||
? secondLevelSubquestions[0].is_complete
|
||||
// Derive the 'externalPhase' from your existing logic:
|
||||
const currentState =
|
||||
overallAnswer.length > 0
|
||||
? finished
|
||||
? StreamingPhase.COMPLETE
|
||||
: StreamingPhase.ANSWER
|
||||
: secondLevelSubquestions[0].context_docs
|
||||
? StreamingPhase.CONTEXT_DOCS
|
||||
: secondLevelSubquestions[0].sub_queries
|
||||
? StreamingPhase.SUB_QUERIES
|
||||
: secondLevelSubquestions[0].question
|
||||
? StreamingPhase.WAITING
|
||||
: StreamingPhase.WAITING
|
||||
: StreamingPhase.WAITING;
|
||||
: 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;
|
||||
|
||||
const message = useOrderedPhases(currentState);
|
||||
// secondLevelSubquestions?.[0]
|
||||
// : secondLevelSubquestions[0].context_docs
|
||||
// ? StreamingPhase.CONTEXT_DOCS
|
||||
// : secondLevelSubquestions[0].sub_queries
|
||||
// ? StreamingPhase.SUB_QUERIES
|
||||
// : secondLevelSubquestions[0].question
|
||||
// ? StreamingPhase.WAITING
|
||||
// : StreamingPhase.WAITING
|
||||
// : StreamingPhase.WAITING;
|
||||
|
||||
// Once the first query token comes through, it should be in the sub queries
|
||||
// Once the first set of documents come through it should be in context docs
|
||||
// Once all of the analysis have started generating, it should say evaluate
|
||||
// Once the refined answer starts generating, it should say Answer
|
||||
// Once the refined answer finishes generating, show COMPARE (we don't have this yet)
|
||||
|
||||
// export const StreamingPhaseText: Record<StreamingPhase, string> = {
|
||||
// [StreamingPhase.WAITING]: "Extracting key concepts",
|
||||
// [StreamingPhase.SUB_QUERIES]: "Identifying additional questions",
|
||||
// [StreamingPhase.CONTEXT_DOCS]: "Reading more documents",
|
||||
// [StreamingPhase.ANSWER]: "Generating refined answer",
|
||||
// [StreamingPhase.EVALUATE]: "Evaluating new context",
|
||||
// [StreamingPhase.COMPARE]: "Comparing results",
|
||||
// [StreamingPhase.COMPLETE]: "Finished",
|
||||
// };
|
||||
|
||||
// Get the array of displayed phases
|
||||
const displayedPhases = useOrderedPhases(currentState);
|
||||
const isDone = displayedPhases.includes(StreamingPhase.COMPLETE);
|
||||
|
||||
// Expand/collapse, hover states
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [shouldShow, setShouldShow] = useState(true);
|
||||
|
||||
// Once "done", hide after a short delay if not hovered
|
||||
useEffect(() => {
|
||||
if (isDone) {
|
||||
const timer = setTimeout(() => {
|
||||
if (!isHovered) {
|
||||
setShouldShow(false);
|
||||
}
|
||||
}, 800); // e.g. 0.8s
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isDone, isHovered]);
|
||||
|
||||
if (!shouldShow) {
|
||||
return null; // entire box disappears
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex loading-text items-center gap-x-2 text-black text-sm font-medium cursor-pointer hover:text-blue-600 transition-colors duration-200">
|
||||
Refining answer...
|
||||
<FiChevronRight
|
||||
className="inline-block text-text-darker"
|
||||
size={16}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="w-80 p-4 bg-white shadow-lg rounded-md">
|
||||
<div className="space-y-4">
|
||||
<p className="text-lg leading-none font-semibold text-gray-800">
|
||||
{message}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
The answer is being refined based on additional context and
|
||||
analysis.
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => toggleInitialAnswerVieinwg()}
|
||||
size="sm"
|
||||
className="w-full"
|
||||
<div
|
||||
className="relative w-full max-w-sm"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{/* Original snippet's tooltip usage */}
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="flex loading-text items-center gap-x-2 text-black text-sm
|
||||
font-medium cursor-pointer hover:text-blue-600
|
||||
transition-colors duration-200"
|
||||
>
|
||||
{isViewingInitialAnswer
|
||||
? "See Live Updates"
|
||||
: "Hide Live Updates"}
|
||||
<FiGlobe className="inline-block mr-2" />
|
||||
</Button>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
Refining Answer
|
||||
<FiChevronRight
|
||||
className={`inline-block transition-transform duration-200 text-text-darker ${
|
||||
isHovered ? "rotate-90" : ""
|
||||
}`}
|
||||
size={16}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="w-80 p-4 bg-white shadow-lg rounded-md"
|
||||
>
|
||||
{/* If not done, show the "Refining" box + a chevron */}
|
||||
|
||||
{/* Expanded area: each displayed phase in order */}
|
||||
{expanded && (
|
||||
<div className="items-start flex flex-col gap-y-2">
|
||||
{currentState !== StreamingPhase.WAITING ? (
|
||||
Array.from(new Set(displayedPhases)).map((phase, index) => {
|
||||
const phaseIndex = displayedPhases.indexOf(phase);
|
||||
// The last displayed item is "running" if not COMPLETE
|
||||
let status = ToggleState.Done;
|
||||
if (
|
||||
index ===
|
||||
Array.from(new Set(displayedPhases)).length - 1
|
||||
) {
|
||||
status = ToggleState.InProgress;
|
||||
}
|
||||
if (phase === StreamingPhase.COMPLETE) {
|
||||
status = ToggleState.Done;
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// "use client";
|
||||
// import {
|
||||
// Tooltip,
|
||||
// TooltipTrigger,
|
||||
// TooltipProvider,
|
||||
// TooltipContent,
|
||||
// } from "@/components/ui/tooltip";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import { FiChevronRight, FiGlobe } from "react-icons/fi";
|
||||
// import {
|
||||
// StreamingPhase,
|
||||
// StreamingPhaseText,
|
||||
// } from "./message/StreamingMessages";
|
||||
// import { SubQuestionDetail } from "./interfaces";
|
||||
// import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// const PHASES_ORDER: StreamingPhase[] = [
|
||||
// StreamingPhase.WAITING,
|
||||
// StreamingPhase.SUB_QUERIES,
|
||||
// StreamingPhase.CONTEXT_DOCS,
|
||||
// StreamingPhase.ANSWER,
|
||||
// StreamingPhase.COMPLETE,
|
||||
// ];
|
||||
|
||||
// export function useOrderedPhases(externalPhase: StreamingPhase) {
|
||||
// const [phaseQueue, setPhaseQueue] = useState<StreamingPhase[]>([]);
|
||||
// const [displayedPhase, setDisplayedPhase] = useState<StreamingPhase>(
|
||||
// StreamingPhase.WAITING
|
||||
// );
|
||||
// const lastDisplayTimestampRef = useRef<number>(Date.now());
|
||||
|
||||
// const getPhaseIndex = (phase: StreamingPhase) => {
|
||||
// return PHASES_ORDER.indexOf(phase);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// setPhaseQueue((prevQueue) => {
|
||||
// const lastQueuedPhase =
|
||||
// prevQueue.length > 0 ? prevQueue[prevQueue.length - 1] : displayedPhase;
|
||||
|
||||
// const lastQueuedIndex = getPhaseIndex(lastQueuedPhase);
|
||||
// const externalIndex = getPhaseIndex(externalPhase);
|
||||
|
||||
// if (externalIndex <= lastQueuedIndex) {
|
||||
// return prevQueue;
|
||||
// }
|
||||
|
||||
// const missingPhases: StreamingPhase[] = [];
|
||||
// for (let i = lastQueuedIndex + 1; i <= externalIndex; i++) {
|
||||
// missingPhases.push(PHASES_ORDER[i]);
|
||||
// }
|
||||
// return [...prevQueue, ...missingPhases];
|
||||
// });
|
||||
// }, [externalPhase, displayedPhase]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (phaseQueue.length === 0) return;
|
||||
// let rafId: number;
|
||||
|
||||
// const processQueue = () => {
|
||||
// const now = Date.now();
|
||||
// const elapsed = now - lastDisplayTimestampRef.current;
|
||||
|
||||
// // Keep this at 1000ms from the original example (unchanged),
|
||||
// // but you can adjust if you want a different visible time in *this* component.
|
||||
// if (elapsed >= 1000) {
|
||||
// setPhaseQueue((prevQueue) => {
|
||||
// if (prevQueue.length > 0) {
|
||||
// const [next, ...rest] = prevQueue;
|
||||
// setDisplayedPhase(next);
|
||||
// lastDisplayTimestampRef.current = Date.now();
|
||||
// return rest;
|
||||
// }
|
||||
// return prevQueue;
|
||||
// });
|
||||
// }
|
||||
// rafId = requestAnimationFrame(processQueue);
|
||||
// };
|
||||
|
||||
// rafId = requestAnimationFrame(processQueue);
|
||||
// return () => {
|
||||
// if (rafId) {
|
||||
// cancelAnimationFrame(rafId);
|
||||
// }
|
||||
// };
|
||||
// }, [phaseQueue]);
|
||||
|
||||
// return StreamingPhaseText[displayedPhase];
|
||||
// }
|
||||
|
||||
// export default function RefinemenetBadge({
|
||||
// secondLevelSubquestions,
|
||||
// toggleInitialAnswerVieinwg,
|
||||
// isViewingInitialAnswer,
|
||||
// }: {
|
||||
// secondLevelSubquestions?: SubQuestionDetail[] | null;
|
||||
// toggleInitialAnswerVieinwg: () => void;
|
||||
// isViewingInitialAnswer: boolean;
|
||||
// }) {
|
||||
// const currentState = secondLevelSubquestions?.[0]
|
||||
// ? secondLevelSubquestions[0].answer
|
||||
// ? secondLevelSubquestions[0].is_complete
|
||||
// ? StreamingPhase.COMPLETE
|
||||
// : StreamingPhase.ANSWER
|
||||
// : secondLevelSubquestions[0].context_docs
|
||||
// ? StreamingPhase.CONTEXT_DOCS
|
||||
// : secondLevelSubquestions[0].sub_queries
|
||||
// ? StreamingPhase.SUB_QUERIES
|
||||
// : secondLevelSubquestions[0].question
|
||||
// ? StreamingPhase.WAITING
|
||||
// : StreamingPhase.WAITING
|
||||
// : StreamingPhase.WAITING;
|
||||
|
||||
// const message = useOrderedPhases(currentState);
|
||||
|
||||
// return (
|
||||
// <TooltipProvider delayDuration={0}>
|
||||
// <Tooltip>
|
||||
// <TooltipTrigger asChild>
|
||||
// <div className="flex loading-text items-center gap-x-2 text-black text-sm font-medium cursor-pointer hover:text-blue-600 transition-colors duration-200">
|
||||
// Refining answer...
|
||||
// <FiChevronRight
|
||||
// className="inline-block text-text-darker"
|
||||
// size={16}
|
||||
// />
|
||||
// </div>
|
||||
// </TooltipTrigger>
|
||||
// <TooltipContent className="w-80 p-4 bg-white shadow-lg rounded-md">
|
||||
// <div className="space-y-4">
|
||||
// <p className="text-lg leading-none font-semibold text-gray-800">
|
||||
// {message}
|
||||
// </p>
|
||||
// <p className="text-sm text-gray-600">
|
||||
// The answer is being refined based on additional context and
|
||||
// analysis.
|
||||
// </p>
|
||||
// <Button
|
||||
// onClick={() => toggleInitialAnswerVieinwg()}
|
||||
// size="sm"
|
||||
// className="w-full"
|
||||
// >
|
||||
// {isViewingInitialAnswer
|
||||
// ? "See Live Updates"
|
||||
// : "Hide Live Updates"}
|
||||
// <FiGlobe className="inline-block mr-2" />
|
||||
// </Button>
|
||||
// </div>
|
||||
// </TooltipContent>
|
||||
// </Tooltip>
|
||||
// </TooltipProvider>
|
||||
// );
|
||||
// }
|
||||
|
@ -13,7 +13,13 @@ export function WebResultIcon({
|
||||
size?: number;
|
||||
}) {
|
||||
const [error, setError] = useState(false);
|
||||
const hostname = new URL(url).hostname;
|
||||
let hostname;
|
||||
try {
|
||||
hostname = new URL(url).hostname;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
hostname = "docs.onyx.app";
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{hostname == "docs.onyx.app" ? (
|
||||
|
Loading…
x
Reference in New Issue
Block a user