mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
1740
frontend/package-lock.json
generated
1740
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,40 +21,40 @@
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.2",
|
||||
"@vercel/analytics": "^0.1.10",
|
||||
"i18next": "^23.11.5",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^3.0.0"
|
||||
"remark-gfm": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"@typescript-eslint/parser": "^5.51.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-config-standard-with-typescript": "^34.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-n": "^15.6.1",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.6.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^15.2.8",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||
"tailwindcss": "^3.2.4",
|
||||
|
||||
3
frontend/src/assets/document.svg
Normal file
3
frontend/src/assets/document.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9294 5.375V12.4583C12.9294 12.8341 12.7801 13.1944 12.5145 13.4601C12.2488 13.7257 11.8885 13.875 11.5127 13.875H3.01274C2.63701 13.875 2.27668 13.7257 2.011 13.4601C1.74532 13.1944 1.59607 12.8341 1.59607 12.4583V2.54167C1.59607 2.16594 1.74532 1.80561 2.011 1.53993C2.27668 1.27426 2.63701 1.125 3.01274 1.125H8.6794M12.9294 5.375V5.25317C12.9293 4.87747 12.78 4.5172 12.5143 4.25158L9.80282 1.54008C9.53721 1.27439 9.17693 1.12508 8.80124 1.125H8.6794M12.9294 5.375H10.0961C9.72035 5.375 9.36001 5.22574 9.09434 4.96007C8.82866 4.69439 8.6794 4.33406 8.6794 3.95833V1.125" stroke="#949494" stroke-width="1.41667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 781 B |
25
frontend/src/assets/sources.svg
Normal file
25
frontend/src/assets/sources.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<svg width="24" height="27" viewBox="0 0 24 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.73517 17.2121L0.538918 17.9125C0.3753 18.0084 0.239616 18.1455 0.145332 18.31C0.0510491 18.4746 0.00144577 18.661 0.00144577 18.8506C0.00144577 19.0403 0.0510491 19.2267 0.145332 19.3912C0.239616 19.5558 0.3753 19.6929 0.538918 19.7888L11.0514 25.9513C11.2181 26.0489 11.4078 26.1004 11.601 26.1004C11.7941 26.1004 11.9838 26.0489 12.1505 25.9513L22.663 19.7888C22.8266 19.6929 22.9623 19.5558 23.0566 19.3912C23.1509 19.2267 23.2005 19.0403 23.2005 18.8506C23.2005 18.661 23.1509 18.4746 23.0566 18.31C22.9623 18.1455 22.8266 18.0084 22.663 17.9125L21.4668 17.2107L13.2511 22.0276C12.7506 22.321 12.1811 22.4757 11.601 22.4757C11.0209 22.4757 10.4513 22.321 9.95087 22.0276L1.73517 17.2121Z" fill="url(#paint0_linear_4013_8178)"/>
|
||||
<path d="M1.73517 11.4121L0.538918 12.1124C0.3753 12.2084 0.239616 12.3454 0.145332 12.51C0.0510491 12.6746 0.00144577 12.8609 0.00144577 13.0506C0.00144577 13.2403 0.0510491 13.4266 0.145332 13.5912C0.239616 13.7558 0.3753 13.8928 0.538918 13.9887L11.0514 20.1512C11.2181 20.2489 11.4078 20.3003 11.601 20.3003C11.7941 20.3003 11.9838 20.2489 12.1505 20.1512L22.663 13.9887C22.8266 13.8928 22.9623 13.7558 23.0566 13.5912C23.1509 13.4266 23.2005 13.2403 23.2005 13.0506C23.2005 12.8609 23.1509 12.6746 23.0566 12.51C22.9623 12.3454 22.8266 12.2084 22.663 12.1124L21.4668 11.4106L13.2511 16.2275C12.7506 16.5209 12.1811 16.6756 11.601 16.6756C11.0209 16.6756 10.4513 16.5209 9.95087 16.2275L1.73517 11.4121Z" fill="url(#paint1_linear_4013_8178)"/>
|
||||
<path d="M12.152 0.149921C11.9849 0.0517579 11.7947 0 11.601 0C11.4072 0 11.217 0.0517579 11.05 0.149921L0.537472 6.31242C0.373854 6.40835 0.23817 6.5454 0.143887 6.70997C0.0496035 6.87454 0 7.06091 0 7.25057C0 7.44024 0.0496035 7.6266 0.143887 7.79117C0.23817 7.95574 0.373854 8.09279 0.537472 8.18872L11.05 14.3512C11.217 14.4494 11.4072 14.5011 11.601 14.5011C11.7947 14.5011 11.9849 14.4494 12.152 14.3512L22.6645 8.18872C22.8281 8.09279 22.9638 7.95574 23.0581 7.79117C23.1523 7.6266 23.2019 7.44024 23.2019 7.25057C23.2019 7.06091 23.1523 6.87454 23.0581 6.70997C22.9638 6.5454 22.8281 6.40835 22.6645 6.31242L12.152 0.149921Z" fill="url(#paint2_linear_4013_8178)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_4013_8178" x1="0.00144577" y1="21.6555" x2="23.2005" y2="21.6555" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#70FDF7"/>
|
||||
<stop offset="0.325" stop-color="#747696"/>
|
||||
<stop offset="0.68" stop-color="#BD5372"/>
|
||||
<stop offset="1" stop-color="#F5A06C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_4013_8178" x1="0.00144577" y1="15.8555" x2="23.2005" y2="15.8555" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#70FDF7"/>
|
||||
<stop offset="0.325" stop-color="#747696"/>
|
||||
<stop offset="0.68" stop-color="#BD5372"/>
|
||||
<stop offset="1" stop-color="#F5A06C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_4013_8178" x1="0" y1="7.25057" x2="23.2019" y2="7.25057" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#70FDF7"/>
|
||||
<stop offset="0.325" stop-color="#747696"/>
|
||||
<stop offset="0.68" stop-color="#BD5372"/>
|
||||
<stop offset="1" stop-color="#F5A06C"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
54
frontend/src/components/Sidebar.tsx
Normal file
54
frontend/src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
|
||||
type SidebarProps = {
|
||||
isOpen: boolean;
|
||||
toggleState: (arg0: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function Sidebar({
|
||||
isOpen,
|
||||
toggleState,
|
||||
children,
|
||||
}: SidebarProps) {
|
||||
const sidebarRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
sidebarRef.current &&
|
||||
!sidebarRef.current.contains(event.target as Node)
|
||||
) {
|
||||
toggleState(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div ref={sidebarRef} className="h-vh relative">
|
||||
<div
|
||||
className={`fixed right-0 top-0 z-50 h-full w-72 transform bg-white shadow-xl transition-all duration-300 dark:bg-chinese-black sm:w-96 ${
|
||||
isOpen ? 'translate-x-[10px]' : 'translate-x-full'
|
||||
} border-l border-[#9ca3af]/10`}
|
||||
>
|
||||
<div className="flex w-full flex-row items-end justify-end px-4 pt-3">
|
||||
<button
|
||||
className="w-7 rounded-full p-2 hover:bg-gray-1000 hover:dark:bg-gun-metal"
|
||||
onClick={() => toggleState(!isOpen)}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex h-full flex-col items-center gap-2 py-4 px-6 text-center">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -62,7 +62,7 @@ export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { sources },
|
||||
query: { sources: sources ?? [] },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -433,3 +433,11 @@ template {
|
||||
.bottom-safe {
|
||||
bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.ellipsis-text {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@@ -76,17 +76,17 @@ export function setLocalPrompt(prompt: string): void {
|
||||
localStorage.setItem('DocsGPTPrompt', prompt);
|
||||
}
|
||||
|
||||
export function setLocalRecentDocs(doc: Doc): void {
|
||||
export function setLocalRecentDocs(doc: Doc | null): void {
|
||||
localStorage.setItem('DocsGPTRecentDocs', JSON.stringify(doc));
|
||||
let namePath = doc.name;
|
||||
if (doc.language === namePath) {
|
||||
let namePath = doc?.name;
|
||||
if (doc?.language === namePath) {
|
||||
namePath = '.project';
|
||||
}
|
||||
|
||||
let docPath = 'default';
|
||||
if (doc.location === 'local') {
|
||||
if (doc?.location === 'local') {
|
||||
docPath = 'local' + '/' + doc.name + '/';
|
||||
} else if (doc.location === 'remote') {
|
||||
} else if (doc?.location === 'remote') {
|
||||
docPath =
|
||||
doc.language + '/' + namePath + '/' + doc.version + '/' + doc.model + '/';
|
||||
}
|
||||
|
||||
@@ -95,8 +95,7 @@ prefListenerMiddleware.startListening({
|
||||
matcher: isAnyOf(setSelectedDocs),
|
||||
effect: (action, listenerApi) => {
|
||||
setLocalRecentDocs(
|
||||
(listenerApi.getState() as RootState).preference.selectedDocs ??
|
||||
([] as unknown as Doc),
|
||||
(listenerApi.getState() as RootState).preference.selectedDocs ?? null,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user