(Minor) Add cleaner search, feedback model, and connector view (#2098)

* add cleaner search, feedback model, and connector view

* Update ChatPage.tsx
This commit is contained in:
pablodanswer
2024-08-09 18:54:31 -07:00
committed by GitHub
parent c8ead6a0dc
commit 54d4526b73
6 changed files with 291 additions and 253 deletions

View File

@@ -417,83 +417,85 @@ export function CCPairIndexingStatusTable({
{!shouldExpand ? "Collapse All" : "Expand All"}
</Button>
</div>
{sortedSources.map((source, ind) => {
const sourceMatches = source
.toLowerCase()
.includes(searchTerm.toLowerCase());
const matchingConnectors = groupedStatuses[source].filter(
(status) =>
(status.name || "")
.toLowerCase()
.includes(searchTerm.toLowerCase())
);
if (sourceMatches || matchingConnectors.length > 0) {
return (
<React.Fragment key={ind}>
<div className="mt-4" />
<SummaryRow
source={source}
summary={groupSummaries[source]}
isOpen={connectorsToggled[source] || false}
onToggle={() => toggleSource(source)}
/>
{connectorsToggled[source] && (
<>
<TableRow className="border border-border">
<TableHeaderCell
className={`w-[${columnWidths.first}]`}
>
Name
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.fifth}]`}
>
Last Indexed
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.second}]`}
>
Activity
</TableHeaderCell>
{isPaidEnterpriseFeaturesEnabled && (
<TableHeaderCell
className={`w-[${columnWidths.fourth}]`}
>
Public
</TableHeaderCell>
)}
<TableHeaderCell
className={`w-[${columnWidths.sixth}]`}
>
Total Docs
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.third}]`}
>
Last Status
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.seventh}]`}
></TableHeaderCell>
</TableRow>
{(sourceMatches
? groupedStatuses[source]
: matchingConnectors
).map((ccPairsIndexingStatus) => (
<ConnectorRow
key={ccPairsIndexingStatus.cc_pair_id}
ccPairsIndexingStatus={ccPairsIndexingStatus}
/>
))}
</>
)}
</React.Fragment>
{sortedSources
.filter((source) => source != "not_applicable")
.map((source, ind) => {
const sourceMatches = source
.toLowerCase()
.includes(searchTerm.toLowerCase());
const matchingConnectors = groupedStatuses[source].filter(
(status) =>
(status.name || "")
.toLowerCase()
.includes(searchTerm.toLowerCase())
);
}
return null;
})}
if (sourceMatches || matchingConnectors.length > 0) {
return (
<React.Fragment key={ind}>
<div className="mt-4" />
<SummaryRow
source={source}
summary={groupSummaries[source]}
isOpen={connectorsToggled[source] || false}
onToggle={() => toggleSource(source)}
/>
{connectorsToggled[source] && (
<>
<TableRow className="border border-border">
<TableHeaderCell
className={`w-[${columnWidths.first}]`}
>
Name
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.fifth}]`}
>
Last Indexed
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.second}]`}
>
Activity
</TableHeaderCell>
{isPaidEnterpriseFeaturesEnabled && (
<TableHeaderCell
className={`w-[${columnWidths.fourth}]`}
>
Public
</TableHeaderCell>
)}
<TableHeaderCell
className={`w-[${columnWidths.sixth}]`}
>
Total Docs
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.third}]`}
>
Last Status
</TableHeaderCell>
<TableHeaderCell
className={`w-[${columnWidths.seventh}]`}
></TableHeaderCell>
</TableRow>
{(sourceMatches
? groupedStatuses[source]
: matchingConnectors
).map((ccPairsIndexingStatus) => (
<ConnectorRow
key={ccPairsIndexingStatus.cc_pair_id}
ccPairsIndexingStatus={ccPairsIndexingStatus}
/>
))}
</>
)}
</React.Fragment>
);
}
return null;
})}
</TableBody>
<div className="invisible w-full pb-40" />

View File

@@ -76,10 +76,6 @@ import { useSidebarVisibility } from "@/components/chat_search/hooks";
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
import FixedLogo from "./shared_chat_search/FixedLogo";
import { getSecondsUntilExpiration } from "@/lib/time";
import {
FullLLMProvider,
LLMProviderDescriptor,
} from "../admin/models/llm/interfaces";
const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;

View File

@@ -6,6 +6,7 @@ import { FiThumbsDown, FiThumbsUp } from "react-icons/fi";
import { ModalWrapper } from "./ModalWrapper";
import {
DislikeFeedbackIcon,
FilledLikeIcon,
LikeFeedbackIcon,
} from "@/components/icons/icons";
@@ -53,19 +54,19 @@ export const FeedbackModal = ({
: predefinedNegativeFeedbackOptions;
return (
<ModalWrapper onClose={onClose} modalClassName="max-w-5xl">
<ModalWrapper onClose={onClose} modalClassName="max-w-3xl">
<>
<h2 className="text-2xl text-emphasis font-bold mb-4 flex">
<div className="mr-1 my-auto">
{feedbackType === "like" ? (
<LikeFeedbackIcon
<FilledLikeIcon
size={20}
className="text-green-500 my-auto mr-2"
/>
) : (
<DislikeFeedbackIcon
<FilledLikeIcon
size={20}
className="text-red-600 my-auto mr-2"
className="rotate-180 text-red-600 my-auto mr-2"
/>
)}
</div>

View File

@@ -1695,6 +1695,29 @@ export const ThumbsUpIconSkeleton = ({
);
};
export const FilledLikeIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<svg
style={{ width: `${size}px`, height: `${size}px` }}
className={`w-[${size}px] h-[${size}px] ` + className}
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
viewBox="0 0 14 14"
>
<path
fill="currentColor"
fill-rule="evenodd"
d="M4.41 12.961a2.5 2.5 0 0 0 1.076.244h5.346a2.5 2.5 0 0 0 2.47-2.114l.626-4.003a2 2 0 0 0-1.976-2.31H8.67V2.422a1.625 1.625 0 0 0-3.044-.794l-2.077 3.71a1.5 1.5 0 0 0-.191.733v5.442a1.5 1.5 0 0 0 .854 1.354l.2.095Zm-3.366-7.44a.996.996 0 0 0-.997.996v5.112a.997.997 0 0 0 .997.997h.496a.5.5 0 0 0 .5-.5V6.02a.5.5 0 0 0-.5-.5h-.496Z"
clip-rule="evenodd"
/>
</svg>
);
};
export const LikeFeedbackIcon = ({
size = 16,
className = defaultTailwindCSS,

View File

@@ -0,0 +1,174 @@
"use client";
import {
Dispatch,
SetStateAction,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { HoverableIcon } from "../Hoverable";
import {
DislikeFeedbackIcon,
LikeFeedbackIcon,
ToggleDown,
} from "../icons/icons";
import { FeedbackType } from "@/app/chat/types";
import { searchState } from "./SearchSection";
import { SettingsContext } from "../settings/SettingsProvider";
import { AnswerSection } from "./results/AnswerSection";
import { Quote, SearchResponse } from "@/lib/search/interfaces";
import { QuotesSection } from "./results/QuotesSection";
export default function SearchAnswer({
searchAnswerExpanded,
setSearchAnswerExpanded,
isFetching,
dedupedQuotes,
searchResponse,
setCurrentFeedback,
searchState,
}: {
searchAnswerExpanded: boolean;
setSearchAnswerExpanded: Dispatch<SetStateAction<boolean>>;
isFetching: boolean;
dedupedQuotes: Quote[];
searchResponse: SearchResponse;
searchState: searchState;
setCurrentFeedback: Dispatch<SetStateAction<[FeedbackType, number] | null>>;
}) {
const [searchAnswerOverflowing, setSearchAnswerOverflowing] = useState(false);
const { quotes, answer, error } = searchResponse;
const answerContainerRef = useRef<HTMLDivElement>(null);
const handleFeedback = (feedbackType: FeedbackType, messageId: number) => {
setCurrentFeedback([feedbackType, messageId]);
};
const settings = useContext(SettingsContext);
useEffect(() => {
const checkOverflow = () => {
if (answerContainerRef.current) {
const isOverflowing =
answerContainerRef.current.scrollHeight >
answerContainerRef.current.clientHeight;
setSearchAnswerOverflowing(isOverflowing);
}
};
checkOverflow();
window.addEventListener("resize", checkOverflow);
return () => {
window.removeEventListener("resize", checkOverflow);
};
}, [answer]);
return (
<div
ref={answerContainerRef}
className={`my-4 ${searchAnswerExpanded ? "min-h-[16rem]" : "h-[16rem]"} overflow-y-hidden p-4 border-2 border-border rounded-lg relative`}
>
<div>
<div className="flex gap-x-2">
<h2 className="text-emphasis font-bold my-auto mb-1 ">AI Answer</h2>
{searchState == "generating" && (
<div key={"generating"} className="relative inline-block">
<span className="loading-text">Generating response...</span>
</div>
)}
{searchState == "citing" && (
<div key={"citing"} className="relative inline-block">
<span className="loading-text">Creating citations...</span>
</div>
)}
{searchState == "searching" && (
<div key={"Reading"} className="relative inline-block">
<span className="loading-text">Searching...</span>
</div>
)}
{searchState == "reading" && (
<div key={"Reading"} className="relative inline-block">
<span className="loading-text">
Reading{settings?.isMobile ? "" : " Documents"}
...
</span>
</div>
)}
{searchState == "analyzing" && (
<div key={"Generating"} className="relative inline-block">
<span className="loading-text">
Running
{settings?.isMobile ? "" : " Analysis"}...
</span>
</div>
)}
</div>
<div className={`pt-1 h-auto border-t border-border w-full`}>
<AnswerSection
answer={answer}
quotes={quotes}
error={error}
isFetching={isFetching}
/>
</div>
{searchAnswerExpanded ||
(!searchAnswerOverflowing && (
<div className="w-full">
{quotes !== null && quotes.length > 0 && answer && (
<QuotesSection quotes={dedupedQuotes} isFetching={isFetching} />
)}
{searchResponse.messageId !== null && (
<div className="absolute right-3 flex bottom-3">
<HoverableIcon
icon={<LikeFeedbackIcon />}
onClick={() =>
handleFeedback(
"like",
searchResponse?.messageId as number
)
}
/>
<HoverableIcon
icon={<DislikeFeedbackIcon />}
onClick={() =>
handleFeedback(
"dislike",
searchResponse?.messageId as number
)
}
/>
</div>
)}
</div>
))}
</div>
{!searchAnswerExpanded && searchAnswerOverflowing && (
<div className="absolute bottom-0 left-0 w-full h-[100px] bg-gradient-to-b from-background/5 via-background/60 to-background/90"></div>
)}
{!searchAnswerExpanded && searchAnswerOverflowing && (
<div className="w-full h-12 absolute items-center content-center flex left-0 px-4 bottom-0">
<button
onClick={() => setSearchAnswerExpanded(true)}
className="flex gap-x-1 items-center justify-center hover:bg-background-100 cursor-pointer max-w-sm text-sm mx-auto w-full bg-background border py-2 rounded-full"
>
Show more
<ToggleDown />
</button>
</div>
)}
</div>
);
}

View File

@@ -31,22 +31,11 @@ import { SIDEBAR_TOGGLED_COOKIE_NAME } from "../resizable/constants";
import { AGENTIC_SEARCH_TYPE_COOKIE_NAME } from "@/lib/constants";
import Cookies from "js-cookie";
import FixedLogo from "@/app/chat/shared_chat_search/FixedLogo";
import { AnswerSection } from "./results/AnswerSection";
import { QuotesSection } from "./results/QuotesSection";
import { QAFeedbackBlock } from "./QAFeedback";
import { usePopup } from "../admin/connectors/Popup";
import { ToggleRight } from "@phosphor-icons/react";
import {
DislikeFeedbackIcon,
LikeFeedbackIcon,
ToggleDown,
ToggleUp,
} from "../icons/icons";
import { CustomTooltip, TooltipGroup } from "../tooltip/CustomTooltip";
import { HoverableIcon } from "../Hoverable";
import { FeedbackType } from "@/app/chat/types";
import { FeedbackModal } from "@/app/chat/modal/FeedbackModal";
import { handleChatFeedback } from "@/app/chat/lib";
import SearchAnswer from "./SearchAnswer";
export type searchState =
| "input"
@@ -111,7 +100,6 @@ export const SearchSection = ({
});
const [agentic, setAgentic] = useState(agenticSearchEnabled);
const [searchAnswerExpanded, setSearchAnswerExpanded] = useState(false);
const toggleAgentic = () => {
Cookies.set(
@@ -376,6 +364,7 @@ export const SearchSection = ({
setSearchState("input");
}
};
const [searchAnswerExpanded, setSearchAnswerExpanded] = useState(false);
const resetInput = (finalized?: boolean) => {
setSweep(false);
@@ -546,32 +535,6 @@ export const SearchSection = ({
[FeedbackType, number] | null
>(null);
//
const [searchAnswerOverflowing, setSearchAnswerOverflowing] = useState(false);
const answerContainerRef = useRef<HTMLDivElement>(null);
const handleFeedback = (feedbackType: FeedbackType, messageId: number) => {
setCurrentFeedback([feedbackType, messageId]);
};
useEffect(() => {
const checkOverflow = () => {
if (answerContainerRef.current) {
const isOverflowing =
answerContainerRef.current.scrollHeight >
answerContainerRef.current.clientHeight;
setSearchAnswerOverflowing(isOverflowing);
}
};
checkOverflow();
window.addEventListener("resize", checkOverflow);
return () => {
window.removeEventListener("resize", checkOverflow);
};
}, [answer]);
const onFeedback = async (
messageId: number,
feedbackType: FeedbackType,
@@ -758,136 +721,15 @@ export const SearchSection = ({
/>
</div>
{!firstSearch && (
<div
ref={answerContainerRef}
className={`my-4 ${searchAnswerExpanded ? "min-h-[16rem]" : "h-[16rem]"} overflow-y-hidden p-4 border-2 border-border rounded-lg relative`}
>
<div>
<div className="flex gap-x-2">
<h2 className="text-emphasis font-bold my-auto mb-1 ">
AI Answer
</h2>
{searchState == "generating" && (
<div
key={"generating"}
className="relative inline-block"
>
<span className="loading-text">
Generating response...
</span>
</div>
)}
{searchState == "citing" && (
<div
key={"citing"}
className="relative inline-block"
>
<span className="loading-text">
Creating citations...
</span>
</div>
)}
{searchState == "searching" && (
<div
key={"Reading"}
className="relative inline-block"
>
<span className="loading-text">Searching...</span>
</div>
)}
{searchState == "reading" && (
<div
key={"Reading"}
className="relative inline-block"
>
<span className="loading-text">
Reading{settings?.isMobile ? "" : " Documents"}
...
</span>
</div>
)}
{searchState == "analyzing" && (
<div
key={"Generating"}
className="relative inline-block"
>
<span className="loading-text">
Running
{settings?.isMobile ? "" : " Analysis"}...
</span>
</div>
)}
</div>
<div
className={`pt-1 h-auto border-t border-border w-full`}
>
<AnswerSection
answer={answer}
quotes={quotes}
error={error}
isFetching={isFetching}
/>
</div>
{searchAnswerExpanded ||
(!searchAnswerOverflowing && (
<div className="w-full">
{quotes !== null &&
quotes.length > 0 &&
answer && (
<QuotesSection
quotes={dedupedQuotes}
isFetching={isFetching}
/>
)}
{searchResponse.messageId !== null && (
<div className="absolute right-3 flex bottom-3">
<HoverableIcon
icon={<LikeFeedbackIcon />}
onClick={() =>
handleFeedback(
"like",
searchResponse?.messageId as number
)
}
/>
<HoverableIcon
icon={<DislikeFeedbackIcon />}
onClick={() =>
handleFeedback(
"dislike",
searchResponse?.messageId as number
)
}
/>
</div>
)}
</div>
))}
</div>
{!searchAnswerExpanded && searchAnswerOverflowing && (
<div className="absolute bottom-0 left-0 w-full h-[100px] bg-gradient-to-b from-background/5 via-background/60 to-background/90"></div>
)}
{!searchAnswerExpanded && searchAnswerOverflowing && (
<div className="w-full h-12 absolute items-center content-center flex left-0 px-4 bottom-0">
<button
onClick={() => setSearchAnswerExpanded(true)}
className="flex gap-x-1 items-center justify-center hover:bg-background-100 cursor-pointer max-w-sm text-sm mx-auto w-full bg-background border py-2 rounded-full"
>
Show more
<ToggleDown />
</button>
</div>
)}
</div>
<SearchAnswer
isFetching={isFetching}
dedupedQuotes={dedupedQuotes}
searchResponse={searchResponse}
setSearchAnswerExpanded={setSearchAnswerExpanded}
searchAnswerExpanded={searchAnswerExpanded}
setCurrentFeedback={setCurrentFeedback}
searchState={searchState}
/>
)}
{!settings?.isMobile && (