mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-20 13:05:49 +02:00
(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:
@@ -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" />
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
|
174
web/src/components/search/SearchAnswer.tsx
Normal file
174
web/src/components/search/SearchAnswer.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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 && (
|
||||
|
Reference in New Issue
Block a user