Merge remote-tracking branch 'refs/remotes/origin/main'

This commit is contained in:
“Shriya
2024-08-21 06:00:19 +05:30
17 changed files with 1513 additions and 1789 deletions

View File

@@ -1,17 +1,27 @@
import { forwardRef, useState } from 'react';
import Avatar from '../components/Avatar';
import CopyButton from '../components/CopyButton';
import remarkGfm from 'remark-gfm';
import { FEEDBACK, MESSAGE_TYPE } from './conversationModels';
import classes from './ConversationBubble.module.css';
import Alert from './../assets/alert.svg';
import Like from './../assets/like.svg?react';
import Dislike from './../assets/dislike.svg?react';
import ReactMarkdown from 'react-markdown';
import { useSelector } from 'react-redux';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import remarkGfm from 'remark-gfm';
import Alert from '../assets/alert.svg';
import DocsGPT3 from '../assets/cute_docsgpt3.svg';
import Dislike from '../assets/dislike.svg?react';
import Document from '../assets/document.svg';
import Like from '../assets/like.svg?react';
import Link from '../assets/link.svg';
import Sources from '../assets/sources.svg';
import Avatar from '../components/Avatar';
import CopyButton from '../components/CopyButton';
import Sidebar from '../components/Sidebar';
import {
selectChunks,
selectSelectedDocs,
} from '../preferences/preferenceSlice';
import classes from './ConversationBubble.module.css';
import { FEEDBACK, MESSAGE_TYPE } from './conversationModels';
const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false;
const ConversationBubble = forwardRef<
@@ -29,15 +39,16 @@ const ConversationBubble = forwardRef<
{ message, type, className, feedback, handleFeedback, sources, retryBtn },
ref,
) {
const [openSource, setOpenSource] = useState<number | null>(null);
const chunks = useSelector(selectChunks);
const selectedDocs = useSelector(selectSelectedDocs);
const [isLikeHovered, setIsLikeHovered] = useState(false);
const [isDislikeHovered, setIsDislikeHovered] = useState(false);
const [isLikeClicked, setIsLikeClicked] = useState(false);
const [isDislikeClicked, setIsDislikeClicked] = useState(false);
const [activeTooltip, setActiveTooltip] = useState<number | null>(null);
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
let bubble;
if (type === 'QUESTION') {
bubble = (
<div ref={ref} className={`flex flex-row-reverse self-end ${className}`}>
@@ -55,18 +66,147 @@ const ConversationBubble = forwardRef<
ref={ref}
className={`flex flex-wrap self-start ${className} group flex-col dark:text-bright-gray`}
>
<div className="flex flex-wrap self-start lg:flex-nowrap">
<Avatar
className="mt-2 h-12 w-12 text-2xl"
avatar={
<img
src={DocsGPT3}
alt="DocsGPT"
className="h-full w-full object-cover"
{DisableSourceFE ||
type === 'ERROR' ||
sources?.length === 0 ||
sources?.some((source) => source.source === 'None') ? null : !sources &&
chunks !== '0' &&
selectedDocs ? (
<div className="mb-4 flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
<div className="my-2 flex flex-row items-center justify-center gap-3">
<Avatar
className="h-[26px] w-[30px] text-xl"
avatar={
<img
src={Sources}
alt="Sources"
className="h-full w-full object-fill"
/>
}
/>
}
/>
<p className="text-base font-semibold">Sources</p>
</div>
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, index) => (
<div
key={index}
className="flex h-28 cursor-pointer flex-col items-start gap-1 rounded-[20px] bg-gray-1000 p-4 text-purple-30 hover:bg-[#F1F1F1] hover:text-[#6D3ECC] dark:bg-gun-metal dark:hover:bg-[#2C2E3C] dark:hover:text-[#8C67D7]"
>
<span className="h-px w-10 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
<span className="h-px w-24 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
<span className="h-px w-16 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
<span className="h-px w-32 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
<span className="h-px w-24 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
<span className="h-px w-20 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
</div>
))}
</div>
</div>
) : (
sources && (
<div className="mb-4 flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
<div className="my-2 flex flex-row items-center justify-center gap-3">
<Avatar
className="h-[26px] w-[30px] text-xl"
avatar={
<img
src={Sources}
alt="Sources"
className="h-full w-full object-fill"
/>
}
/>
<p className="text-base font-semibold">Sources</p>
</div>
<div className="ml-3 mr-5 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
{sources?.slice(0, 3)?.map((source, index) => (
<div key={index} className="relative">
<div
className="h-28 cursor-pointer rounded-[20px] bg-gray-1000 p-4 hover:bg-[#F1F1F1] dark:bg-gun-metal dark:hover:bg-[#2C2E3C]"
onMouseOver={() => setActiveTooltip(index)}
onMouseOut={() => setActiveTooltip(null)}
>
<p className="ellipsis-text h-12 break-words text-xs">
{source.text}
</p>
<div
className={`mt-[14px] flex flex-row items-center gap-[6px] underline-offset-2 ${
source.source && source.source !== 'local'
? 'hover:text-[#007DFF] hover:underline dark:hover:text-[#48A0FF]'
: ''
}`}
onClick={() =>
source.source && source.source !== 'local'
? window.open(
source.source,
'_blank',
'noopener, noreferrer',
)
: null
}
>
<img
src={Document}
alt="Document"
className="h-[17px] w-[17px] object-fill"
/>
<p
className="mt-[2px] truncate text-xs"
title={
source.source && source.source !== 'local'
? source.source
: source.title
}
>
{source.source && source.source !== 'local'
? source.source
: source.title}
</p>
</div>
</div>
{activeTooltip === index && (
<div
className={`absolute left-1/2 z-30 max-h-48 w-40 translate-x-[-50%] translate-y-[3px] rounded-xl bg-[#FBFBFB] p-4 text-black shadow-xl dark:bg-chinese-black dark:text-chinese-silver sm:w-56`}
onMouseOver={() => setActiveTooltip(index)}
onMouseOut={() => setActiveTooltip(null)}
>
<p className="max-h-[164px] overflow-y-auto break-words rounded-md text-sm">
{source.text}
</p>
</div>
)}
</div>
))}
{(sources?.length ?? 0) > 3 && (
<div
className="flex h-24 cursor-pointer flex-col-reverse rounded-[20px] bg-gray-1000 p-4 text-purple-30 hover:bg-[#F1F1F1] hover:text-[#6D3ECC] dark:bg-gun-metal dark:hover:bg-[#2C2E3C] dark:hover:text-[#8C67D7]"
onClick={() => setIsSidebarOpen(true)}
>
<p className="ellipsis-text h-22 text-xs">{`View ${
sources?.length ? sources.length - 3 : 0
} more`}</p>
</div>
)}
</div>
</div>
</div>
)
)}
<div className="flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
<div className="my-2 flex flex-row items-center justify-center gap-3">
<Avatar
className="h-[34px] w-[34px] text-2xl"
avatar={
<img
src={DocsGPT3}
alt="DocsGPT"
className="h-full w-full object-cover"
/>
}
/>
<p className="text-base font-semibold">Answer</p>
</div>
<div
className={`ml-2 mr-5 flex max-w-[90vw] rounded-[28px] bg-gray-1000 py-[14px] px-7 dark:bg-gun-metal md:max-w-[70vw] lg:max-w-[50vw] ${
type === 'ERROR'
@@ -86,19 +226,19 @@ const ConversationBubble = forwardRef<
className="whitespace-pre-wrap break-normal leading-normal"
remarkPlugins={[remarkGfm]}
components={{
code({ node, inline, className, children, ...props }) {
code(props) {
const { children, className, node, ref, ...rest } = props;
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
return match ? (
<div className="group relative">
<SyntaxHighlighter
{...rest}
PreTag="div"
children={String(children).replace(/\n$/, '')}
language={match[1]}
{...props}
style={vscDarkPlus}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
/>
<div
className={`absolute right-3 top-3 lg:invisible
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} `}
@@ -165,51 +305,9 @@ const ConversationBubble = forwardRef<
>
{message}
</ReactMarkdown>
{DisableSourceFE ||
type === 'ERROR' ||
!sources ||
sources.length === 0 ? null : (
<>
<span className="mt-3 h-px w-full bg-[#DEDEDE]"></span>
<div className="mt-3 flex w-full flex-row flex-wrap items-center justify-start gap-2">
<div className="py-1 text-base font-semibold">Sources:</div>
<div className="flex flex-row flex-wrap items-center justify-start gap-2">
{sources?.map((source, index) => (
<div
key={index}
className={`max-w-xs cursor-pointer rounded-[28px] px-4 py-1 sm:max-w-sm md:max-w-md ${
openSource === index
? 'bg-[#007DFF]'
: 'bg-[#D7EBFD] hover:bg-[#BFE1FF]'
}`}
onClick={() =>
source.source !== 'local'
? window.open(
source.source,
'_blank',
'noopener, noreferrer',
)
: setOpenSource(openSource === index ? null : index)
}
>
<p
className={`truncate text-center text-base font-medium ${
openSource === index
? 'text-white'
: 'text-[#007DFF]'
}`}
>
{index + 1}. {source.title.substring(0, 45)}
</p>
</div>
))}
</div>
</div>
</>
)}
</div>
</div>
<div className="my-2 flex justify-start lg:ml-12">
<div className="my-2 ml-2 flex justify-start">
<div
className={`relative mr-5 block items-center justify-center lg:invisible
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''}`}
@@ -292,19 +390,14 @@ const ConversationBubble = forwardRef<
</>
)}
</div>
{sources && openSource !== null && sources[openSource] && (
<div className="ml-10 mt-12 max-w-[300px] break-words rounded-xl bg-blue-200 p-2 dark:bg-gun-metal sm:max-w-[800px] lg:mt-2">
<p className="m-1 w-3/4 truncate text-xs text-gray-500 dark:text-bright-gray">
Source: {sources[openSource].title}
</p>
<div className="m-2 rounded-xl border-2 border-gray-200 bg-white p-2 dark:border-chinese-silver dark:bg-dark-charcoal">
<p className="text-break text-black dark:text-bright-gray">
{sources[openSource].text}
</p>
</div>
</div>
{sources && (
<Sidebar
isOpen={isSidebarOpen}
toggleState={(state: boolean) => {
setIsSidebarOpen(state);
}}
children={<AllSources sources={sources} />}
/>
)}
</div>
);
@@ -312,4 +405,49 @@ const ConversationBubble = forwardRef<
return bubble;
});
type AllSourcesProps = {
sources: { title: string; text: string; source: string }[];
};
function AllSources(sources: AllSourcesProps) {
return (
<div className="h-full w-full">
<div className="w-full">
<p className="text-left text-xl">{`${sources.sources.length} Sources`}</p>
<div className="mx-1 mt-2 h-[0.8px] w-full rounded-full bg-[#C4C4C4]/40 lg:w-[95%] "></div>
</div>
<div className="mt-6 flex h-[90%] w-60 flex-col items-center gap-4 overflow-y-auto sm:w-80">
{sources.sources.map((source, index) => (
<div
key={index}
className="min-h-32 w-full rounded-[20px] bg-gray-1000 p-4 dark:bg-[#28292E]"
>
<span className="flex flex-row">
<p
title={source.title}
className="ellipsis-text break-words text-left text-sm font-semibold"
>
{`${index + 1}. ${source.title}`}
</p>
{source.source && source.source !== 'local' ? (
<img
src={Link}
alt="Link"
className="h-3 w-3 cursor-pointer object-fill"
onClick={() =>
window.open(source.source, '_blank', 'noopener, noreferrer')
}
></img>
) : null}
</span>
<p className="mt-3 max-h-24 overflow-y-auto break-words rounded-md text-left text-xs text-black dark:text-chinese-silver">
{source.text}
</p>
</div>
))}
</div>
</div>
);
}
export default ConversationBubble;

View File

@@ -116,6 +116,7 @@ export function handleFetchAnswerSteaming(
prompt_id: promptId,
chunks: chunks,
token_limit: token_limit,
isNoneDoc: selectedDocs === null,
},
signal,
)
@@ -184,6 +185,7 @@ export function handleSearch(
history,
chunks: chunks,
token_limit: token_limit,
isNoneDoc: selectedDocs === null,
})
.then((response) => response.json())
.then((data) => {

View File

@@ -62,7 +62,7 @@ export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
dispatch(
updateStreamingSource({
index: state.conversation.queries.length - 1,
query: { sources },
query: { sources: sources ?? [] },
}),
);
});