update- reorg

This commit is contained in:
pablodanswer
2025-01-23 12:13:44 -08:00
committed by Evan Lohn
parent dd494d2daa
commit d32d1c6079
7 changed files with 211 additions and 182 deletions

View File

@ -826,7 +826,7 @@ export function ChatInputBar({
chatState == "toolBuilding" || chatState == "toolBuilding" ||
chatState == "loading" ? ( chatState == "loading" ? (
<StopGeneratingIcon <StopGeneratingIcon
size={10} size={8}
className="text-emphasis m-auto text-white flex-none" className="text-emphasis m-auto text-white flex-none"
/> />
) : ( ) : (

View File

@ -76,9 +76,9 @@ import {
StreamingPhase, StreamingPhase,
StreamingPhaseText, StreamingPhaseText,
useStreamingMessages, useStreamingMessages,
useOrderedPhases,
} from "./StreamingMessages"; } from "./StreamingMessages";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import RefinemenetBadge from "../refinmentBadge";
export const AgenticMessage = ({ export const AgenticMessage = ({
secondLevelAssistantMessage, secondLevelAssistantMessage,
@ -374,22 +374,6 @@ export const AgenticMessage = ({
); );
}, [streamedContent, markdownComponents]); }, [streamedContent, markdownComponents]);
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);
const includeMessageSwitcher = const includeMessageSwitcher =
currentMessageInd !== undefined && currentMessageInd !== undefined &&
onMessageSelection && onMessageSelection &&
@ -495,45 +479,16 @@ export const AgenticMessage = ({
Answer Answer
</div> </div>
{secondLevelSubquestions && {true ? (
secondLevelSubquestions.length > 0 && <RefinemenetBadge
secondLevelGenerating ? ( secondLevelSubquestions={secondLevelSubquestions}
<Popover> toggleInitialAnswerVieinwg={() => {
<PopoverTrigger 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>
</PopoverTrigger>
<PopoverContent 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={() =>
setIsViewingInitialAnswer( setIsViewingInitialAnswer(
!isViewingInitialAnswer !isViewingInitialAnswer
) );
} }}
size="sm" isViewingInitialAnswer={isViewingInitialAnswer}
className="w-full" />
>
{isViewingInitialAnswer
? "See Live Updates"
: "Hide Live Updates"}
<FiGlobe className="inline-block mr-2" />
</Button>
</div>
</PopoverContent>
</Popover>
) : ( ) : (
secondLevelAssistantMessage && ( secondLevelAssistantMessage && (
<Badge <Badge

View File

@ -1,8 +1,12 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { OnyxDocument } from "@/lib/search/interfaces"; import { OnyxDocument } from "@/lib/search/interfaces";
import { ResultIcon } from "@/components/chat_search/sources/SourceCard"; import {
ResultIcon,
SeeMoreBlock,
} from "@/components/chat_search/sources/SourceCard";
import { openDocument } from "@/lib/search/utils"; import { openDocument } from "@/lib/search/utils";
import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay"; import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay";
import { ValidSources } from "@/lib/types";
interface SourcesDisplayProps { interface SourcesDisplayProps {
documents: OnyxDocument[]; documents: OnyxDocument[];
@ -103,19 +107,16 @@ export const SourcesDisplay: React.FC<SourcesDisplayProps> = ({
))} ))}
{hasMoreDocuments && ( {hasMoreDocuments && (
<button <SeeMoreBlock
onClick={toggleDocumentSelection} toggled={false}
className={`max-w-[260px] h-[80px] p-3 bg-[#f1eee8] hover:bg-[#ebe7de] cursor-pointer rounded-lg flex flex-col items-start justify-between transition-opacity duration-300 ${ toggleDocumentSelection={toggleDocumentSelection}
animateEntrance ? "opacity-100" : "opacity-100" uniqueSources={
}`} Array.from(
> new Set(documents.map((doc) => doc.source_type))
<div className="flex items-center gap-1"> ) as ValidSources[]
{documents.slice(3, 6).map((doc, index) => ( }
<ResultIcon key={index} doc={doc} size={14} /> webSourceDomains={documents.map((doc) => doc.link)}
))} />
</div>
<div className="text-[#4a4a4a] text-xs font-medium">Show All</div>
</button>
)} )}
</div> </div>
</div> </div>

View File

@ -31,86 +31,12 @@ interface SubQuestionProgress {
answerCharIndex: number; answerCharIndex: number;
} }
const PHASES_ORDER: StreamingPhase[] = [
StreamingPhase.WAITING,
StreamingPhase.SUB_QUERIES,
StreamingPhase.CONTEXT_DOCS,
StreamingPhase.ANSWER,
StreamingPhase.COMPLETE,
];
export const PHASE_MIN_MS = 800; // Minimum phase duration in ms export const PHASE_MIN_MS = 800; // Minimum phase duration in ms
function canTransition(p: SubQuestionProgress) { function canTransition(p: SubQuestionProgress) {
return Date.now() - p.phaseStartTime >= PHASE_MIN_MS; return Date.now() - p.phaseStartTime >= PHASE_MIN_MS;
} }
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];
}
const DOC_DELAY_MS = 100; const DOC_DELAY_MS = 100;
export const useStreamingMessages = ( export const useStreamingMessages = (

View File

@ -506,9 +506,12 @@ const SubQuestionsDisplay: React.FC<SubQuestionsDisplayProps> = ({
useEffect(() => { useEffect(() => {
if (documents && documents.length > 0) { if (documents && documents.length > 0) {
setTimeout(() => { setTimeout(
() => {
setShownDocuments(documents); setShownDocuments(documents);
}, 1500); },
overallAnswerGenerating ? 1500 : 0
);
} }
}, [documents]); }, [documents]);
@ -639,7 +642,7 @@ const SubQuestionsDisplay: React.FC<SubQuestionsDisplayProps> = ({
documents={documents} documents={documents}
isLast={ isLast={
!showSummarizing && !showSummarizing &&
memoizedSubQuestions.length > index + 1 && memoizedSubQuestions.length == index + 1 &&
!( !(
showSecondLevel && showSecondLevel &&
memoizedSecondLevelQuestions && memoizedSecondLevelQuestions &&
@ -689,7 +692,7 @@ const SubQuestionsDisplay: React.FC<SubQuestionsDisplayProps> = ({
documents={documents} documents={documents}
isLast={ isLast={
!showSummarizing && !showSummarizing &&
memoizedSecondLevelQuestions.length > index + 1 memoizedSecondLevelQuestions.length == index + 1
} }
isFirst={false} isFirst={false}
setPresentingDocument={setPresentingDocument} setPresentingDocument={setPresentingDocument}

View File

@ -0,0 +1,152 @@
"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>
);
}

View File

@ -71,37 +71,29 @@ export function SeeMoreBlock({
); );
const numOfWebSourcesToDisplay = 3 - filteredUniqueSources.length; const numOfWebSourcesToDisplay = 3 - filteredUniqueSources.length;
return ( return (
<div <button
onClick={toggleDocumentSelection} onClick={toggleDocumentSelection}
className={` className={`max-w-[260px] min-w-[100px] h-[80px] p-3 bg-[#f1eee8] hover:bg-[#ebe7de] cursor-pointer rounded-lg flex flex-col items-start justify-between transition-opacity duration-300`}
cursor-pointer rounded-lg flex-none transition-all duration-500 hover:bg-background-dark/80 bg-background-dark/60 px-3 py-2
`}
> >
<div className="flex gap-y-2 flex-col items-start text-sm"> <div className="flex items-center gap-1">
<p
onClick={toggleDocumentSelection}
className="flex-1 mr-1 font-semibold text-text-900 overflow-hidden text-ellipsis whitespace-nowrap"
>
{toggled ? "Hide Results" : "Show Sources"}
</p>
<div className="flex-shrink-0 flex gap-x-1 items-center">
{filteredUniqueSources.slice(0, 3).map((source, index) => ( {filteredUniqueSources.slice(0, 3).map((source, index) => (
<SourceIcon key={index} sourceType={source} iconSize={16} /> <SourceIcon key={index} sourceType={source} iconSize={14} />
))} ))}
{/* {webSourceDomains {webSourceDomains
.slice(0, numOfWebSourcesToDisplay) .slice(0, numOfWebSourcesToDisplay)
.map((domain, ind) => ( .map((domain, index) => (
<WebResultIcon key={ind} url={domain} /> <WebResultIcon key={index} url={domain} size={14} />
))} ))}
<WebResultIcon url={domain} />
))} */}
{uniqueSources.length > 3 && ( {uniqueSources.length > 3 && (
<span className="text-xs text-text-700 font-semibold ml-1"> <span className="text-xs text-[#4a4a4a] font-medium ml-1">
+{uniqueSources.length - 3} +{uniqueSources.length - 3}
</span> </span>
)} )}
</div> </div>
<div className="text-text-darker text-xs font-semibold">
{toggled ? "Hide Results" : "Show All"}
</div> </div>
</div> </button>
); );
} }