Merge pull request #1670 from ManishMadan2882/main

Figma consolidation
This commit is contained in:
Alex
2025-03-13 15:21:03 +00:00
committed by GitHub
46 changed files with 1067 additions and 613 deletions

View File

@@ -37,12 +37,14 @@ export default function Hero({
<Fragment key={key}>
<button
onClick={() => handleQuestion({ question: demo.query })}
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw] bg-white dark:bg-raisin-black focus:outline-none"
className={`w-full rounded-full border bg-transparent px-6 py-4 text-left min-w-11/12 sm:min-w-[362px] focus:outline-none
border-dark-gray text-just-black hover:bg-cultured
dark:border-dim-gray dark:text-chinese-white dark:hover:bg-charleston-green`}
>
<p className="mb-1 font-semibold text-black-1000 dark:text-bright-gray">
{demo.header}
</p>
<span className="text-gray-700 dark:text-gray-300">
<span className="text-gray-700 dark:text-gray-300 opacity-60">
{demo.query}
</span>
</button>

View File

@@ -240,7 +240,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
ref={navRef}
className={`${
!navOpen && '-ml-96 md:-ml-[18rem]'
} duration-20 fixed top-0 z-20 flex h-full w-72 flex-col border-r-[1px] border-b-0 bg-white transition-all dark:border-r-purple-taupe dark:bg-chinese-black dark:text-white`}
} duration-20 fixed top-0 z-20 flex h-full w-72 flex-col border-r-[1px] border-b-0 bg-lotion dark:bg-chinese-black transition-all dark:border-r-purple-taupe dark:text-white`}
>
<div
className={'visible mt-2 flex h-[6vh] w-full justify-between md:h-12'}
@@ -283,8 +283,8 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
}}
className={({ isActive }) =>
`${
isActive ? 'bg-gray-3000 dark:bg-transparent' : ''
} group sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray hover:bg-gray-3000 dark:border-purple-taupe dark:text-white dark:hover:bg-transparent`
isActive ? 'bg-transparent' : ''
} group sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray dark:border-purple-taupe dark:text-white hover:bg-transparent`
}
>
<img
@@ -339,7 +339,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
</div>
<div className="flex h-auto flex-col justify-end text-eerie-black dark:text-white">
<div className="flex flex-col-reverse border-b-[1px] dark:border-b-purple-taupe">
<div className="relative my-4 mx-4 flex gap-2">
<div className="relative my-4 mx-4 flex gap-4 items-center">
<SourceDropdown
options={docs}
selectedDocs={selectedDocs}
@@ -354,8 +354,10 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
}}
/>
<img
className="mt-2 h-9 w-9 hover:cursor-pointer"
className="hover:cursor-pointer"
src={UploadIcon}
width={28}
height={25}
alt="Upload document"
onClick={() => {
setUploadModalState('ACTIVE');
@@ -385,7 +387,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
<img
src={SettingGear}
alt="Settings"
className="ml-2 w-5 filter dark:invert"
className="ml-2 w- filter dark:invert"
/>
<p className="my-auto text-sm text-eerie-black dark:text-white">
{t('settings.label')}

View File

@@ -0,0 +1,11 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5819_12451)">
<path d="M8.40039 14.3C5.06706 14.3 2.53372 12.4334 0.533724 8.63338L0.400391 8.30005L0.533724 7.96672C2.53372 4.16672 5.06706 2.30005 8.40039 2.30005C11.7337 2.30005 14.2671 4.16672 16.2671 7.96672L16.4004 8.30005L16.2671 8.63338C14.2671 12.4334 11.7337 14.3 8.40039 14.3ZM1.93372 8.30005C3.60039 11.4334 5.73372 12.9667 8.40039 12.9667C11.0671 12.9667 13.2004 11.4334 14.8671 8.30005C13.2004 5.16672 11.0671 3.63338 8.40039 3.63338C5.73372 3.63338 3.60039 5.16672 1.93372 8.30005Z" fill="#747474"/>
<path d="M8.40072 11.6333C6.53405 11.6333 5.06738 10.1667 5.06738 8.30001C5.06738 6.43334 6.53405 4.96667 8.40072 4.96667C10.2674 4.96667 11.734 6.43334 11.734 8.30001C11.734 10.1667 10.2674 11.6333 8.40072 11.6333ZM8.40072 6.30001C7.26738 6.30001 6.40072 7.16667 6.40072 8.30001C6.40072 9.43334 7.26738 10.3 8.40072 10.3C9.53405 10.3 10.4007 9.43334 10.4007 8.30001C10.4007 7.16667 9.53405 6.30001 8.40072 6.30001Z" fill="#747474"/>
</g>
<defs>
<clipPath id="clip0_5819_12451">
<rect width="16" height="16" fill="white" transform="translate(0.400391 0.300049)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#ffffff"><path d="M.01 0h24v24h-24V0z" fill="none"/><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#747474"><path d="M.01 0h24v24h-24V0z" fill="none"/><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 735 B

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 855.6 867.3" style="enable-background:new 0 0 855.6 867.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<g id="Layer_2_00000062898455470481303740000016245162613792141746_">
<g id="avatar">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="281.0639" y1="214.2932" x2="737.1139" y2="670.3433" gradientTransform="matrix(1 0 0 -1 0 874.2527)">
<stop offset="0" style="stop-color:#526676"/>
<stop offset="1" style="stop-color:#6B9DBF"/>
</linearGradient>
<path class="st0" d="M725.1,663.3c-0.8,11.7-8,19.2-17.7,25c-25,14.9-51.5,26.3-79.2,35.1c-70.2,22.4-142.4,30.9-215.8,29.8
c-62.5-0.9-123.7-9.8-183.4-28.1c-32-9.8-62.8-22.3-91.7-39.3c-8.9-5.3-13.8-12.1-15-22.5c-2.5-21.2-2.4-42.6,0.3-63.8
c3.1-24,18-40.7,35.5-55.3c19.7-16.5,43.2-26,67.6-33c28.6-8.3,57.4-15.8,86-23.8c9.8,25.7,22.8,47,39,63.5
c22.4,22.8,50.2,35.3,78.4,35.3c41.9,0,80.6-26.8,103.4-71.6c4.2-8.3,8-16.8,11.3-25.5c7.4,2.5,14.9,4.6,22.5,6.5
c16.2,4.1,32.5,7.3,48.6,11.6c26.3,7.1,51.6,16.5,73.8,32.9c24.1,17.8,37.6,41.5,38.6,71.3C727.8,628.7,726.2,646,725.1,663.3z
M317.7,351c2.7,2.5,4.7,5.7,5.8,9.3c3,10.3,4.8,21.1,8.2,31.3c8.2,24.8,20.8,47.4,41,64.4c18.7,15.8,40.5,23.3,61.9,23.3
c31.7,0,62.5-16.5,81.5-47.2c13.6-22.1,21.8-45.9,26.2-71.3c0.6-3.4,1.7-8,4.1-9.4c21.5-13,25.1-34,25.7-56.2
c0.2-8.3-1.7-9.6-8-10.3c0-17,0.6-33.8-0.2-50.6c-0.4-10.4-2.1-20.6-5-30.6c-8.3-27.4-25.6-46.7-53.6-55.1
c-4.3-1.3-10.5,0.2-12.9-3.9c-11-19.4-31.6-25.6-54.4-25.6c-19.3,0-40.2,4.4-58.6,9.2c-21.1,5.4-40.2,14.6-54.8,31.5
c-20.4,23.8-26.2,52.1-24.7,82.4c0.7,14.6,2.9,29.1,4.4,44.1c-10.9,0.9-11.2,4.8-11,13.6C293.9,321.5,301.4,336.5,317.7,351z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,132 @@
import { SyntheticEvent, useRef, useEffect, CSSProperties } from 'react';
export interface MenuOption {
icon?: string;
label: string;
onClick: (event: SyntheticEvent) => void;
variant?: 'primary' | 'danger';
iconClassName?: string;
iconWidth?: number;
iconHeight?: number;
}
interface ContextMenuProps {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
options: MenuOption[];
anchorRef: React.RefObject<HTMLElement>;
className?: string;
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
offset?: { x: number; y: number };
}
export default function ContextMenu({
isOpen,
setIsOpen,
options,
anchorRef,
className = '',
position = 'bottom-right',
offset = { x: 0, y: 8 },
}: ContextMenuProps) {
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
!anchorRef.current?.contains(event.target as Node)
) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
return () =>
document.removeEventListener('mousedown', handleClickOutside);
}
}, [isOpen, setIsOpen]);
if (!isOpen) return null;
const getMenuPosition = (): CSSProperties => {
if (!anchorRef.current) return {};
const rect = anchorRef.current.getBoundingClientRect();
const scrollY = window.scrollY || document.documentElement.scrollTop;
const scrollX = window.scrollX || document.documentElement.scrollLeft;
let top = rect.bottom + scrollY + offset.y;
let left = rect.right + scrollX + offset.x;
switch (position) {
case 'bottom-left':
left = rect.left + scrollX - offset.x;
break;
case 'top-right':
top = rect.top + scrollY - offset.y;
break;
case 'top-left':
top = rect.top + scrollY - offset.y;
left = rect.left + scrollX - offset.x;
break;
// bottom-right is default
}
return {
position: 'fixed',
top: `${top}px`,
left: `${left}px`,
};
};
return (
<div
ref={menuRef}
className={`fixed z-50 ${className}`}
style={{ ...getMenuPosition() }}
onClick={(e) => e.stopPropagation()}
>
<div
className="flex w-32 flex-col rounded-xl text-sm shadow-xl md:w-36 dark:bg-charleston-green-2 bg-lotion"
style={{ minWidth: '144px' }}
>
{options.map((option, index) => (
<button
key={index}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
option.onClick(event);
setIsOpen(false);
}}
className={`
flex justify-start items-center gap-4 p-3
transition-colors duration-200 ease-in-out
${index === 0 ? 'rounded-t-xl' : ''}
${index === options.length - 1 ? 'rounded-b-xl' : ''}
${
option.variant === 'danger'
? 'dark:text-red-2000 dark:hover:bg-charcoal-grey text-rosso-corsa hover:bg-bright-gray'
: 'dark:text-bright-gray dark:hover:bg-charcoal-grey text-eerie-black hover:bg-bright-gray'
}
`}
>
{option.icon && (
<img
width={option.iconWidth || 16}
height={option.iconHeight || 16}
src={option.icon}
alt={option.label}
className={`cursor-pointer hover:opacity-75 ${option.iconClassName || ''}`}
/>
)}
<span>{option.label}</span>
</button>
))}
</div>
</div>
);
}

View File

@@ -1,18 +1,22 @@
import copy from 'copy-to-clipboard';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import CheckMark from '../assets/checkmark.svg?react';
import Copy from '../assets/copy.svg?react';
export default function CoppyButton({
export default function CopyButton({
text,
colorLight,
colorDark,
showText = false,
}: {
text: string;
colorLight?: string;
colorDark?: string;
showText?: boolean;
}) {
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
const [isCopyHovered, setIsCopyHovered] = useState(false);
@@ -25,29 +29,30 @@ export default function CoppyButton({
};
return (
<div
className={`flex items-center justify-center rounded-full p-2 ${
isCopyHovered
? `bg-[#EEEEEE] dark:bg-purple-taupe`
: `bg-[${colorLight ? colorLight : '#FFFFFF'}] dark:bg-[${colorDark ? colorDark : 'transparent'}]`
}`}
<button
onClick={() => handleCopyClick(text)}
onMouseEnter={() => setIsCopyHovered(true)}
onMouseLeave={() => setIsCopyHovered(false)}
className="flex items-center gap-2"
>
{copied ? (
<CheckMark
className="cursor-pointer stroke-green-2000"
onMouseEnter={() => setIsCopyHovered(true)}
onMouseLeave={() => setIsCopyHovered(false)}
/>
) : (
<Copy
className="w-4 cursor-pointer fill-none"
onClick={() => {
handleCopyClick(text);
}}
onMouseEnter={() => setIsCopyHovered(true)}
onMouseLeave={() => setIsCopyHovered(false)}
/>
<div
className={`flex items-center justify-center rounded-full p-2 ${
isCopyHovered
? `bg-[#EEEEEE] dark:bg-purple-taupe`
: `bg-[${colorLight ? colorLight : '#FFFFFF'}] dark:bg-[${colorDark ? colorDark : 'transparent'}]`
}`}
>
{copied ? (
<CheckMark className="cursor-pointer stroke-green-2000" />
) : (
<Copy className="w-4 cursor-pointer fill-none" />
)}
</div>
{showText && (
<span className="text-xs text-gray-600 dark:text-gray-400">
{copied ? t('conversation.copied') : t('conversation.copy')}
</span>
)}
</div>
</button>
);
}

View File

@@ -11,6 +11,7 @@ function Dropdown({
rounded = 'xl',
border = 'border-2',
borderColor = 'silver',
darkBorderColor = 'dim-gray',
showEdit,
onEdit,
showDelete,
@@ -38,6 +39,7 @@ function Dropdown({
rounded?: 'xl' | '3xl';
border?: 'border' | 'border-2';
borderColor?: string;
darkBorderColor?: string;
showEdit?: boolean;
onEdit?: (value: { name: string; id: string; type: string }) => void;
showDelete?: boolean;
@@ -77,7 +79,7 @@ function Dropdown({
>
<button
onClick={() => setIsOpen(!isOpen)}
className={`flex w-full cursor-pointer items-center justify-between ${border} border-${borderColor} bg-white px-5 py-3 dark:border-${borderColor}/40 dark:bg-transparent ${
className={`flex w-full cursor-pointer items-center justify-between ${border} border-${borderColor} bg-white px-5 py-3 dark:border-${darkBorderColor} dark:bg-transparent ${
isOpen ? `${borderTopRadius}` : `${borderRadius}`
}`}
>
@@ -114,7 +116,7 @@ function Dropdown({
</button>
{isOpen && (
<div
className={`absolute left-0 right-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl ${border} border-${borderColor} bg-white shadow-lg dark:border-${borderColor}/40 dark:bg-dark-charcoal`}
className={`absolute left-0 right-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl ${border} border-${borderColor} bg-white shadow-lg dark:border-${darkBorderColor} dark:bg-dark-charcoal`}
>
{options.map((option: any, index) => (
<div

View File

@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
type DropdownMenuProps = {
name: string;
@@ -6,6 +7,12 @@ type DropdownMenuProps = {
onSelect: (value: string) => void;
defaultValue?: string;
icon?: string;
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
anchorRef?: React.RefObject<HTMLElement>;
className?: string;
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
offset?: { x: number; y: number };
};
export default function DropdownMenu({
@@ -14,24 +21,33 @@ export default function DropdownMenu({
onSelect,
defaultValue = 'none',
icon,
isOpen: controlledIsOpen,
onOpenChange,
anchorRef,
className = '',
position = 'bottom-right',
offset = { x: 0, y: 8 },
}: DropdownMenuProps) {
const dropdownRef = React.useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = React.useState(false);
const [internalIsOpen, setInternalIsOpen] = React.useState(false);
const [selectedOption, setSelectedOption] = React.useState(
options.find((option) => option.value === defaultValue) || options[0],
);
const handleToggle = () => {
setIsOpen(!isOpen);
};
const isOpen =
controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
const setIsOpen = onOpenChange || setInternalIsOpen;
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
!dropdownRef.current.contains(event.target as Node) &&
!anchorRef?.current?.contains(event.target as Node)
) {
setIsOpen(false);
}
};
const handleClickOption = (optionId: number) => {
setIsOpen(false);
setSelectedOption(options[optionId]);
@@ -39,26 +55,40 @@ export default function DropdownMenu({
};
React.useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
return () =>
document.removeEventListener('mousedown', handleClickOutside);
}
}, [isOpen]);
if (!isOpen) return null;
const getMenuPosition = (): React.CSSProperties => {
if (!anchorRef?.current) return {};
const rect = anchorRef.current.getBoundingClientRect();
const top = rect.bottom + offset.y;
const left = rect.right + offset.x;
return {
position: 'fixed',
top: `${top}px`,
left: `${left}px`,
zIndex: 9999,
};
}, []);
return (
<div className="static inline-block text-left" ref={dropdownRef}>
<button
onClick={handleToggle}
className="flex w-20 cursor-pointer flex-row gap-1 rounded-3xl border-purple-30/25 bg-purple-30 p-2 text-xs text-white hover:bg-[#6F3FD1] focus:outline-none"
>
{icon && <img src={icon} alt="OptionIcon" className="h-4 w-4" />}
{selectedOption.value !== 'never' ? selectedOption.label : name}
</button>
};
// Use a portal to render the dropdown outside the table flow
return ReactDOM.createPortal(
<div
ref={dropdownRef}
style={{ ...getMenuPosition() }}
onClick={(e) => e.stopPropagation()}
>
<div
className={`absolute z-50 right-0 mt-1 w-28 transform rounded-md bg-transparent shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${
isOpen
? 'scale-100 opacity-100'
: 'pointer-events-none scale-95 opacity-0'
}`}
className={`w-28 transform rounded-md bg-white dark:bg-dark-charcoal shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${className}`}
>
<div
role="menu"
@@ -83,6 +113,7 @@ export default function DropdownMenu({
))}
</div>
</div>
</div>
</div>,
document.body,
);
}

View File

@@ -1,4 +1,5 @@
import { InputProps } from './types';
import { useRef } from 'react';
const Input = ({
id,
@@ -7,13 +8,13 @@ const Input = ({
value,
isAutoFocused = false,
placeholder,
label,
required = false,
maxLength,
className,
className = '',
colorVariant = 'silver',
borderVariant = 'thick',
children,
labelBgClassName = 'bg-white dark:bg-raisin-black',
onChange,
onPaste,
onKeyDown,
@@ -27,15 +28,27 @@ const Input = ({
thin: 'border',
thick: 'border-2',
};
const inputRef = useRef<HTMLInputElement>(null);
return (
<div className="relative">
<div className={`relative ${className}`}>
<input
className={`h-[42px] w-full rounded-full px-3 py-1 outline-none dark:bg-transparent dark:text-white ${className} ${colorStyles[colorVariant]} ${borderStyles[borderVariant]}`}
ref={inputRef}
className={`h-[42px] w-full rounded-full px-3 py-1
bg-transparent outline-none
text-jet dark:text-bright-gray
placeholder-transparent
${colorStyles[colorVariant]}
${borderStyles[borderVariant]}
[&:-webkit-autofill]:bg-transparent
[&:-webkit-autofill]:appearance-none
[&:-webkit-autofill_selected]:bg-transparent`}
type={type}
id={id}
name={name}
autoFocus={isAutoFocused}
placeholder={placeholder}
placeholder={placeholder || ''}
maxLength={maxLength}
value={value}
onChange={onChange}
@@ -45,17 +58,20 @@ const Input = ({
>
{children}
</input>
{label && (
<div className="absolute -top-2 left-2">
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver flex items-center">
{label}
{required && (
<span className="text-[#D30000] dark:text-[#D42626] ml-0.5">
*
</span>
)}
</span>
</div>
{placeholder && (
<label
htmlFor={id}
className={`absolute left-3 -top-2.5 px-2 text-xs transition-all
peer-placeholder-shown:top-2.5 peer-placeholder-shown:left-3 peer-placeholder-shown:text-base
peer-placeholder-shown:text-gray-4000 peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs
peer-focus:text-gray-4000 dark:text-silver dark:peer-placeholder-shown:text-gray-400
cursor-none pointer-events-none ${labelBgClassName}`}
>
{placeholder}
{required && (
<span className="text-[#D30000] dark:text-[#D42626] ml-0.5">*</span>
)}
</label>
)}
</div>
);

View File

@@ -77,8 +77,8 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
onClick={() => setActiveTab(tab)}
className={`snap-start h-9 rounded-3xl px-4 font-bold transition-colors ${
activeTab === tab
? 'bg-neutral-200 text-neutral-900 dark:bg-dark-charcoal dark:text-white'
: 'text-neutral-700 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-white'
? 'bg-[#F4F4F5] text-neutral-900 dark:bg-dark-charcoal dark:text-white'
: 'text-neutral-700 hover:text-neutral-900 dark:text-neutral-300 dark:hover:text-white'
}`}
role="tab"
aria-selected={activeTab === tab}

View File

@@ -54,7 +54,7 @@ function SourceDropdown({
<div className="relative w-5/6 rounded-3xl" ref={dropdownRef}>
<button
onClick={() => setIsDocsListOpen(!isDocsListOpen)}
className={`flex w-full cursor-pointer items-center border border-silver bg-white p-[14px] dark:bg-transparent ${
className={`flex w-full cursor-pointer items-center border border-silver bg-white p-[11px] dark:bg-transparent ${
isDocsListOpen
? 'rounded-t-3xl dark:border-silver/40'
: 'rounded-3xl dark:border-purple-taupe'

View File

@@ -6,9 +6,10 @@ type ToggleSwitchProps = {
className?: string;
label?: string;
disabled?: boolean;
activeColor?: string;
inactiveColor?: string;
size?: 'small' | 'medium' | 'large';
labelPosition?: 'left' | 'right';
id?: string;
ariaLabel?: string;
};
const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
@@ -17,37 +18,65 @@ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
className = '',
label,
disabled = false,
activeColor = 'bg-purple-30',
inactiveColor = 'bg-transparent',
size = 'medium',
labelPosition = 'left',
id,
ariaLabel,
}) => {
// Size configurations
const sizeConfig = {
small: {
box: 'h-5 w-9',
toggle: 'h-4 w-4 left-0.5 top-0.5',
translate: 'translate-x-full',
},
medium: {
box: 'h-8 w-14',
toggle: 'h-6 w-6 left-1 top-1',
translate: 'translate-x-full',
},
large: {
box: 'h-10 w-16',
toggle: 'h-8 w-8 left-1 top-1',
translate: 'translate-x-full',
},
};
const { box, toggle, translate } = sizeConfig[size];
return (
<label
className={`cursor-pointer select-none justify-between flex flex-row items-center ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`}
htmlFor={id}
className={`cursor-pointer select-none flex flex-row items-center ${
labelPosition === 'right' ? 'flex-row-reverse' : ''
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`}
>
{label && (
<span className="mr-2 text-eerie-black dark:text-white">{label}</span>
<span
className={`text-eerie-black dark:text-white ${
labelPosition === 'left' ? 'mr-1' : 'ml-1'
}`}
>
{label}
</span>
)}
<div className="relative">
<input
type="checkbox"
id={id}
checked={checked}
onChange={(e) => onChange(e.target.checked)}
className="sr-only"
disabled={disabled}
id={id}
aria-label={ariaLabel}
/>
<div
className={`box block h-8 w-14 rounded-full border border-purple-30 ${
checked
? `${activeColor} dark:${activeColor}`
: `${inactiveColor} dark:${inactiveColor}`
className={`block ${box} rounded-full ${
checked ? 'bg-north-texas-green' : 'bg-silver dark:bg-charcoal-grey'
}`}
></div>
<div
className={`absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full transition ${
checked ? 'translate-x-full bg-silver' : 'bg-purple-30'
className={`absolute ${toggle} flex items-center justify-center rounded-full transition bg-white opacity-80 ${
checked ? `${translate} bg-silver` : ''
}`}
></div>
</div>

View File

@@ -8,10 +8,10 @@ export type InputProps = {
maxLength?: number;
name?: string;
placeholder?: string;
label?: string;
required?: boolean;
className?: string;
children?: React.ReactElement;
labelBgClassName?: string;
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
) => void;

View File

@@ -294,19 +294,18 @@ export default function Conversation() {
return (
<div className="flex flex-col gap-1 h-full justify-end ">
{conversationId && queries.length > 0 && (
<div className="absolute top-4 right-20 z-10 ">
{' '}
<div className="flex mt-2 items-center gap-4 ">
<div className="absolute top-4 right-20">
<div className="flex mt-2 items-center gap-4">
{isMobile && queries.length > 0 && (
<button
title="Open New Chat"
onClick={() => {
newChat();
}}
className="hover:bg-bright-gray dark:hover:bg-[#28292E]"
className="hover:bg-bright-gray dark:hover:bg-[#28292E] rounded-full p-2"
>
<img
className=" h-5 w-5 filter dark:invert "
className="h-5 w-5 filter dark:invert"
alt="NewChat"
src={newChatIcon}
/>
@@ -318,22 +317,27 @@ export default function Conversation() {
onClick={() => {
setShareModalState(true);
}}
className=" hover:bg-bright-gray dark:hover:bg-[#28292E]"
className="hover:bg-bright-gray dark:hover:bg-[#28292E] rounded-full p-2"
>
<img
className=" h-5 w-5 filter dark:invert"
className="h-5 w-5 filter dark:invert"
alt="share"
src={ShareIcon}
/>
</button>
</div>
{isShareModalOpen && (
<ShareConversationModal
close={() => {
setShareModalState(false);
}}
conversationId={conversationId}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black/50 dark:bg-gray-alpha" />
<div className="relative z-50 w-full max-w-md rounded-3xl">
<ShareConversationModal
close={() => {
setShareModalState(false);
}}
conversationId={conversationId}
/>
</div>
</div>
)}
</div>
)}
@@ -382,10 +386,10 @@ export default function Conversation() {
)}
</div>
<div className="flex w-11/12 flex-col items-end self-center rounded-2xl bg-opacity-0 z-3 sm:w-[62%] h-auto py-1">
<div className="flex flex-col items-end self-center rounded-2xl bg-opacity-0 z-3 w-[calc(min(742px,92%))] h-auto py-1">
<div
{...getRootProps()}
className="flex w-full items-center rounded-[40px] border border-silver bg-white dark:bg-raisin-black"
className="flex w-full items-center rounded-[40px] border dark:border-grey border-dark-gray bg-lotion dark:bg-charleston-green-3"
>
<label htmlFor="file-upload" className="sr-only">
{t('modals.uploadDoc.label')}
@@ -399,7 +403,7 @@ export default function Conversation() {
ref={inputRef}
tabIndex={1}
placeholder={t('inputPlaceholder')}
className={`inputbox-style w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-transparent py-5 text-base leading-tight opacity-100 focus:outline-none dark:bg-transparent dark:text-bright-gray dark:placeholder-bright-gray dark:placeholder-opacity-50`}
className={`inputbox-style w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-lotion dark:bg-charleston-green-3 py-5 text-base leading-tight opacity-100 focus:outline-none dark:text-bright-gray dark:placeholder-bright-gray dark:placeholder-opacity-50`}
onInput={handleInput}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
@@ -423,7 +427,7 @@ export default function Conversation() {
className="flex items-center justify-center"
>
<img
className="ml-[4px] h-6 w-6 text-white"
className="ml-[4px] h-6 w-6 text-white filter dark:invert-[0.45] invert-[0.35]"
src={isDarkTheme ? SendDark : Send}
alt={t('send')}
/>
@@ -432,7 +436,7 @@ export default function Conversation() {
)}
</div>
<p className="text-gray-595959 hidden w-[100vw] self-center bg-transparent py-2 text-center text-xs dark:text-bright-gray md:inline md:w-full">
<p className="text-gray-4000 hidden w-[100vw] self-center bg-transparent py-2 text-center text-xs dark:text-sonic-silver md:inline md:w-full">
{t('tagline')}
</p>
</div>

View File

@@ -5,10 +5,14 @@ import { useTranslation } from 'react-i18next';
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 {
vscDarkPlus,
oneLight,
} from 'react-syntax-highlighter/dist/cjs/styles/prism';
import rehypeKatex from 'rehype-katex';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import { useDarkTheme } from '../hooks';
import DocsGPT3 from '../assets/cute_docsgpt3.svg';
import ChevronDown from '../assets/chevron-down.svg';
@@ -18,7 +22,7 @@ import Edit from '../assets/edit.svg';
import Like from '../assets/like.svg?react';
import Link from '../assets/link.svg';
import Sources from '../assets/sources.svg';
import UserIcon from '../assets/user.png';
import UserIcon from '../assets/user.svg';
import Accordion from '../components/Accordion';
import Avatar from '../components/Avatar';
import CopyButton from '../components/CopyButton';
@@ -69,6 +73,7 @@ const ConversationBubble = forwardRef<
ref,
) {
const { t } = useTranslation();
const [isDarkTheme] = useDarkTheme();
// const bubbleRef = useRef<HTMLDivElement | null>(null);
const chunks = useSelector(selectChunks);
const selectedDocs = useSelector(selectSelectedDocs);
@@ -113,7 +118,7 @@ const ConversationBubble = forwardRef<
style={{
wordBreak: 'break-word',
}}
className="text-sm sm:text-base ml-2 mr-2 flex items-center rounded-[28px] bg-purple-30 py-[14px] px-[19px] text-white max-w-full whitespace-pre-wrap leading-normal"
className="text-sm sm:text-base ml-2 mr-2 flex items-center rounded-[28px] bg-gradient-to-b from-medium-purple to-slate-blue py-[14px] px-[19px] text-white max-w-full whitespace-pre-wrap leading-normal"
>
{message}
</div>
@@ -122,7 +127,7 @@ const ConversationBubble = forwardRef<
setIsEditClicked(true);
setEditInputBox(message);
}}
className={`flex-shrink-0 h-fit mt-3 p-2 cursor-pointer rounded-full hover:bg-[#35363B] flex items-center ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
className={`flex-shrink-0 h-fit mt-3 p-2 cursor-pointer rounded-full hover:bg-light-silver dark:hover:bg-[#35363B] flex items-center ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
>
<img src={Edit} alt="Edit" className="cursor-pointer" />
</button>
@@ -138,29 +143,29 @@ const ConversationBubble = forwardRef<
onChange={(e) => {
setEditInputBox(e.target.value);
}}
onKeyDown={(e) => {
if(e.key === 'Enter' && !e.shiftKey){
e.preventDefault();
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleEditClick();
}
}}
rows={5}
value={editInputBox}
className="w-full resize-none border-2 border-black dark:border-white rounded-3xl px-4 py-3 text-base leading-relaxed text-black dark:bg-raisin-black dark:text-white focus:outline-none focus:ring-2 focus:ring-[#CDB5FF]"
className="w-full resize-none border border-silver dark:border-philippine-grey rounded-3xl px-4 py-3 text-base leading-relaxed text-carbon dark:text-chinese-white dark:bg-raisin-black focus:outline-none"
/>
<div className="flex items-center justify-end gap-2">
<button
className="rounded-full bg-[#CDB5FF] hover:bg-[#E1D3FF] px-4 py-2 text-purple-30 text-sm font-medium"
onClick={handleEditClick}
>
{t('conversation.edit.update')}
</button>
<button
className="px-4 py-2 text-purple-30 text-sm hover:underline"
className="px-4 py-2 text-purple-30 text-sm font-semibold hover:text-chinese-black-2 dark:hover:text-[#B9BCBE] hover:bg-gainsboro dark:hover:bg-onyx-2 transition-colors rounded-full"
onClick={() => setIsEditClicked(false)}
>
{t('conversation.edit.cancel')}
</button>
<button
className="rounded-full bg-purple-30 hover:bg-violets-are-blue dark:hover:bg-royal-purple px-4 py-2 text-white text-sm font-medium transition-colors"
onClick={handleEditClick}
>
{t('conversation.edit.update')}
</button>
</div>
</div>
)}
@@ -341,7 +346,7 @@ const ConversationBubble = forwardRef<
</p>
</div>
<div
className={`fade-in-bubble 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] ${
className={`fade-in-bubble ml-2 mr-5 flex max-w-[90vw] rounded-[28px] bg-gray-1000 py-[18px] px-7 dark:bg-gun-metal md:max-w-[70vw] lg:max-w-[50vw] ${
type === 'ERROR'
? 'relative flex-row items-center rounded-full border border-transparent bg-[#FFE7E7] p-2 py-5 text-sm font-normal text-red-3000 dark:border-red-2000 dark:text-white'
: 'flex-col rounded-3xl'
@@ -355,25 +360,32 @@ const ConversationBubble = forwardRef<
code(props) {
const { children, className, node, ref, ...rest } = props;
const match = /language-(\w+)/.exec(className || '');
const language = match ? match[1] : '';
return match ? (
<div className="group relative">
<SyntaxHighlighter
{...rest}
PreTag="div"
language={match[1]}
style={vscDarkPlus}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
<div
className={`absolute right-3 top-3 lg:invisible
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} `}
>
<div className="group relative rounded-[14px] overflow-hidden border border-light-silver dark:border-raisin-black">
<div className="flex justify-between items-center px-2 py-1 bg-platinum dark:bg-eerie-black-2">
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
{language}
</span>
<CopyButton
text={String(children).replace(/\n$/, '')}
/>
</div>
<SyntaxHighlighter
{...rest}
PreTag="div"
language={language}
style={isDarkTheme ? vscDarkPlus : oneLight}
className="!mt-0"
customStyle={{
margin: 0,
borderRadius: 0,
scrollbarWidth: 'thin',
}}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
</div>
) : (
<code className="whitespace-pre-line rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal dark:bg-independence dark:text-bright-gray">

View File

@@ -18,6 +18,9 @@ import { selectConversationId } from '../preferences/preferenceSlice';
import { ActiveState } from '../models/misc';
import { ShareConversationModal } from '../modals/ShareConversationModal';
import { useTranslation } from 'react-i18next';
import ContextMenu from '../components/ContextMenu';
import { MenuOption } from '../components/ContextMenu';
import { useOutsideAlerter } from '../hooks';
interface ConversationProps {
name: string;
@@ -128,6 +131,50 @@ export default function ConversationTile({
}
};
const menuOptions: MenuOption[] = [
{
icon: Share,
label: t('convTile.share'),
onClick: (event: SyntheticEvent) => {
event.stopPropagation();
setShareModalState(true);
setOpen(false);
},
variant: 'primary',
iconWidth: 14,
iconHeight: 14,
},
{
icon: Edit,
label: t('convTile.rename'),
onClick: handleEditConversation,
variant: 'primary',
},
{
icon: Trash,
label: t('convTile.delete'),
onClick: (event: SyntheticEvent) => {
event.stopPropagation();
setDeleteModalState('ACTIVE');
setOpen(false);
},
iconWidth: 18,
iconHeight: 18,
variant: 'danger',
},
];
useOutsideAlerter(
tileRef,
() => {
if (isEdit) {
onClear();
}
},
[isEdit],
true,
);
return (
<>
<div
@@ -136,16 +183,18 @@ export default function ConversationTile({
setIsHovered(true);
}}
onMouseLeave={() => {
setIsHovered(false);
if (!isEdit) {
setIsHovered(false);
}
}}
onClick={() => {
onCoversationClick();
conversationId !== conversation.id &&
selectConversation(conversation.id);
}}
className={`my-auto mx-4 mt-4 flex h-9 cursor-pointer items-center justify-between pl-4 gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${
conversationId === conversation.id || isOpen || isHovered
? 'bg-gray-100 dark:bg-[#28292E]'
className={`my-auto mx-4 mt-4 flex h-9 cursor-pointer items-center justify-between pl-4 gap-4 rounded-3xl hover:bg-bright-gray dark:hover:bg-dark-charcoal ${
conversationId === conversation.id || isOpen || isHovered || isEdit
? 'bg-bright-gray dark:bg-dark-charcoal'
: ''
}`}
>
@@ -160,13 +209,13 @@ export default function ConversationTile({
onKeyDown={handleRenameKeyDown}
/>
) : (
<p className="my-auto overflow-hidden overflow-ellipsis whitespace-nowrap text-sm font-normal leading-6 text-eerie-black dark:text-white">
<p className="my-auto overflow-hidden overflow-ellipsis whitespace-nowrap text-sm font-normal leading-6 text-eerie-black dark:text-bright-gray">
{conversationName}
</p>
)}
</div>
{(conversationId === conversation.id || isHovered || isOpen) && (
<div className="flex text-white dark:text-[#949494]" ref={menuRef}>
<div className="flex text-white dark:text-sonic-silver" ref={menuRef}>
{isEdit ? (
<div className="flex gap-1">
<img
@@ -204,64 +253,14 @@ export default function ConversationTile({
<img src={threeDots} width={8} />
</button>
)}
{isOpen && (
<div
className="flex-start absolute z-30 flex w-32 translate-x-1 translate-y-5 flex-col rounded-xl bg-stone-100 text-sm text-black shadow-xl dark:bg-chinese-black dark:text-chinese-silver md:w-36"
style={{
top: `${(tileRef.current?.getBoundingClientRect().top ?? 0) + window.scrollY + 8}px`,
}}
>
<button
onClick={(event: SyntheticEvent) => {
event.stopPropagation();
setShareModalState(true);
setOpen(false);
}}
className="flex-start flex items-center gap-4 rounded-t-xl p-3 hover:bg-bright-gray dark:hover:bg-dark-charcoal"
>
<img
src={Share}
alt="Share"
width={14}
height={14}
className="cursor-pointer hover:opacity-50"
id={`img-${conversation.id}`}
/>
<span>{t('convTile.share')}</span>
</button>
<button
onClick={handleEditConversation}
className="flex-start flex items-center gap-4 p-3 hover:bg-bright-gray dark:hover:bg-dark-charcoal"
>
<img
src={Edit}
alt="Edit"
width={16}
height={16}
className="cursor-pointer hover:opacity-50"
id={`img-${conversation.id}`}
/>
<span>{t('convTile.rename')}</span>
</button>
<button
onClick={(event: SyntheticEvent) => {
event.stopPropagation();
setDeleteModalState('ACTIVE');
setOpen(false);
}}
className="flex-start flex items-center gap-3 rounded-b-xl p-2 text-red-700 hover:bg-bright-gray dark:hover:bg-dark-charcoal"
>
<img
src={Trash}
alt="Delete"
width={24}
height={24}
className="cursor-pointer hover:opacity-50"
/>
<span>{t('convTile.delete')}</span>
</button>
</div>
)}
<ContextMenu
isOpen={isOpen}
setIsOpen={setOpen}
options={menuOptions}
anchorRef={tileRef}
position="bottom-right"
offset={{ x: 1, y: 8 }}
/>
</div>
)}
</div>

View File

@@ -231,8 +231,7 @@ export const sharedConversationSlice = createSlice({
return state;
}
state.status = 'failed';
state.queries[state.queries.length - 1].error =
'Something went wrong. Please check your internet connection.';
state.queries[state.queries.length - 1].error = 'Something went wrong';
});
},
});

View File

@@ -485,25 +485,31 @@ template {
::-webkit-scrollbar {
width: 10;
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 50px white inset;
/* Light mode specific autofill styles */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-text-fill-color: #343541 !important;
-webkit-box-shadow: 0 0 0 30px transparent inset !important;
transition: background-color 5000s ease-in-out 0s;
caret-color: #343541;
}
input:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 50px white inset;
/* Dark mode specific autofill styles */
.dark input:-webkit-autofill,
.dark input:-webkit-autofill:hover,
.dark input:-webkit-autofill:focus,
.dark input:-webkit-autofill:active {
-webkit-text-fill-color: #E5E7EB !important;
-webkit-box-shadow: 0 0 0 30px transparent inset !important;
background-color: transparent !important;
caret-color: #E5E7EB;
}
@media (prefers-color-scheme: dark) {
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 50px rgb(68, 70, 84) inset;
-webkit-text-fill-color: white;
}
input:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 50px rgb(68, 70, 84) inset;
-webkit-text-fill-color: white;
}
/* Additional autocomplete dropdown styles for dark mode */
.dark input:-webkit-autofill::first-line {
color: #E5E7EB;
}
.inputbox-style {

View File

@@ -50,7 +50,8 @@
"medium": "Medium",
"high": "High",
"unlimited": "Unlimited",
"default": "Default"
"default": "Default",
"add": "Add"
},
"documents": {
"title": "This table contains all the documents that are available to you and those you have uploaded",
@@ -72,6 +73,7 @@
"monthly": "Monthly"
},
"actions": "Actions",
"view": "View",
"deleteWarning": "Are you sure you want to delete \"{{name}}\"?"
},
"apiKeys": {
@@ -97,7 +99,7 @@
},
"messages": "Messages",
"tokenUsage": "Token Usage",
"feedback": "Feedback",
"userFeedback": "User Feedback",
"filterPlaceholder": "Filter",
"none": "None",
"positiveFeedback": "Positive Feedback",

View File

@@ -43,7 +43,7 @@
"prompt": "Prompt Activo",
"deleteAllLabel": "Eliminar todas las conversaciones",
"deleteAllBtn": "Eliminar todo",
"addNew": "Agregar Nuevo",
"add": "Añadir",
"convHistory": "Historial de conversaciones",
"none": "Ninguno",
"low": "Bajo",
@@ -72,6 +72,7 @@
"monthly": "Mensual"
},
"actions": "Acciones",
"view": "Ver",
"deleteWarning": "¿Estás seguro de que deseas eliminar \"{{name}}\"?"
},
"apiKeys": {
@@ -97,7 +98,7 @@
},
"messages": "Mensajes",
"tokenUsage": "Uso de Tokens",
"feedback": "Retroalimentación",
"userFeedback": "Retroalimentación del Usuario",
"filterPlaceholder": "Filtrar",
"none": "Ninguno",
"positiveFeedback": "Retroalimentación Positiva",

View File

@@ -50,7 +50,8 @@
"medium": "中",
"high": "高",
"unlimited": "無制限",
"default": "デフォルト"
"default": "デフォルト",
"add": "追加"
},
"documents": {
"label": "ドキュメント",
@@ -71,6 +72,7 @@
"monthly": "毎月"
},
"actions": "アクション",
"view": "表示",
"deleteWarning": "\"{{name}}\"を削除してもよろしいですか?"
},
"apiKeys": {
@@ -100,7 +102,8 @@
"filterPlaceholder": "フィルター",
"none": "なし",
"positiveFeedback": "肯定的なフィードバック",
"negativeFeedback": "否定的なフィードバック"
"negativeFeedback": "否定的なフィードバック",
"userFeedback": "ユーザーフィードバック"
},
"logs": {
"label": "ログ",

View File

@@ -50,7 +50,8 @@
"medium": "Средний",
"high": "Высокий",
"unlimited": "Без ограничений",
"default": "По умолчанию"
"default": "По умолчанию",
"add": "Добавить"
},
"documents": {
"title": "Эта таблица содержит все документы, которые доступны вам и те, которые вы загрузили",
@@ -72,6 +73,7 @@
"monthly": "Ежемесячно"
},
"actions": "Действия",
"view": "Просмотр",
"deleteWarning": "Вы уверены, что хотите удалить \"{{name}}\"?"
},
"apiKeys": {
@@ -97,7 +99,7 @@
},
"messages": "Сообщения",
"tokenUsage": "Использование токена",
"feedback": "Обратная связь",
"userFeedback": "Обратная Связь Пользователя",
"filterPlaceholder": "Фильтр",
"none": "Нет",
"positiveFeedback": "Положительная обратная связь",

View File

@@ -50,7 +50,8 @@
"medium": "中",
"high": "高",
"unlimited": "無限制",
"default": "預設"
"default": "預設",
"add": "添加"
},
"documents": {
"title": "此表格包含所有可供您使用的文件以及您上傳的文件",
@@ -72,6 +73,7 @@
"monthly": "每月"
},
"actions": "操作",
"view": "查看",
"deleteWarning": "您確定要刪除 \"{{name}}\" 嗎?"
},
"apiKeys": {
@@ -97,7 +99,7 @@
},
"messages": "訊息",
"tokenUsage": "Token 使用量",
"feedback": "饋",
"userFeedback": "使用者反饋",
"filterPlaceholder": "篩選",
"none": "無",
"positiveFeedback": "正向回饋",

View File

@@ -50,7 +50,8 @@
"medium": "中",
"high": "高",
"unlimited": "无限",
"default": "默认"
"default": "默认",
"add": "添加"
},
"documents": {
"title": "此表格包含所有可供您使用的文档以及您上传的文档",
@@ -72,6 +73,7 @@
"monthly": "每月"
},
"actions": "操作",
"view": "查看",
"deleteWarning": "您确定要删除 \"{{name}}\" 吗?"
},
"apiKeys": {
@@ -97,7 +99,7 @@
},
"messages": "消息",
"tokenUsage": "令牌使用",
"feedback": "反馈",
"userFeedback": "用户反馈",
"filterPlaceholder": "筛选",
"none": "无",
"positiveFeedback": "正向反馈",

View File

@@ -59,6 +59,7 @@ export default function AddActionModal({
setFunctionNameError(!isValidFunctionName(value));
}}
borderVariant="thin"
labelBgClassName="bg-white dark:bg-charleston-green-2"
placeholder={'Enter name'}
/>
<p
@@ -74,7 +75,7 @@ export default function AddActionModal({
<div className="mt-3 flex flex-row-reverse gap-1 px-3">
<button
onClick={handleAddAction}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
>
Add
</button>

View File

@@ -94,57 +94,60 @@ export default function AddToolModal({
{modalState === 'ACTIVE' && (
<WrapperComponent
close={() => setModalState('INACTIVE')}
className="h-[85vh] w-[90vw] md:w-[75vw]"
className="max-w-[950px] w-[90vw] md:w-[85vw] lg:w-[75vw] h-[85vh]"
>
<div className="flex flex-col gap-4 h-full">
<div className="flex flex-col h-full">
<div>
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
{t('settings.tools.selectToolSetup')}
</h2>
<div className="mt-5 flex flex-col sm:grid sm:grid-cols-3 gap-4 h-[73vh] overflow-auto px-3 py-px">
<div className="mt-5 h-[73vh] overflow-auto px-3 py-px">
{loading ? (
<div className="h-full col-span-3 flex items-center justify-center">
<div className="h-full flex items-center justify-center">
<Spinner />
</div>
) : (
availableTools.map((tool, index) => (
<div
role="button"
tabIndex={0}
key={index}
className="h-52 w-full p-6 border rounded-2xl border-silver dark:border-[#4D4E58] flex flex-col justify-between dark:bg-[#32333B] cursor-pointer hover:border-[#9d9d9d] hover:dark:border-[#717179]"
onClick={() => {
setSelectedTool(tool);
handleAddTool(tool);
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 auto-rows-fr pb-2">
{availableTools.map((tool, index) => (
<div
role="button"
tabIndex={0}
key={index}
className="h-52 w-full p-6 border rounded-2xl border-light-gainsboro dark:border-arsenic bg-white-3000 dark:bg-gunmetal flex flex-col justify-between cursor-pointer hover:border-[#9d9d9d] hover:dark:border-[#717179]"
onClick={() => {
setSelectedTool(tool);
handleAddTool(tool);
}
}}
>
<div className="w-full">
<div className="px-1 w-full flex items-center justify-between">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
className="h-8 w-8"
/>
</div>
<div className="mt-[9px]">
<p
title={tool.displayName}
className="px-1 text-sm font-semibold text-eerie-black dark:text-white leading-relaxed capitalize truncate"
>
{tool.displayName}
</p>
<p className="mt-1 px-1 h-24 overflow-auto text-sm text-gray-600 dark:text-[#8a8a8c] leading-relaxed">
{tool.description}
</p>
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
setSelectedTool(tool);
handleAddTool(tool);
}
}}
>
<div className="w-full">
<div className="px-1 w-full flex items-center justify-between">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
className="h-6 w-6"
alt={`${tool.name} icon`}
/>
</div>
<div className="mt-[9px]">
<p
title={tool.displayName}
className="px-1 text-[13px] font-semibold text-raisin-black-light dark:text-bright-gray leading-relaxed capitalize truncate"
>
{tool.displayName}
</p>
<p className="mt-1 px-1 h-24 overflow-auto text-[12px] text-old-silver dark:text-sonic-silver-light leading-relaxed">
{tool.description}
</p>
</div>
</div>
</div>
</div>
))
))}
</div>
)}
</div>
</div>

View File

@@ -61,6 +61,7 @@ export default function ChunkModal({
onChange={(e) => setTitle(e.target.value)}
borderVariant="thin"
placeholder={'Enter title'}
labelBgClassName="bg-white dark:bg-charleston-green-2"
></Input>
</div>
<div className="mt-6 relative px-3">
@@ -83,7 +84,7 @@ export default function ChunkModal({
handleSubmit(title, chunkText);
setModalState('INACTIVE');
}}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
>
Add
</button>
@@ -123,15 +124,13 @@ export default function ChunkModal({
Edit Chunk
</h2>
<div className="mt-6 relative px-3">
<span className="z-10 absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
Title
</span>
<Input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
borderVariant="thin"
placeholder={'Enter title'}
labelBgClassName="bg-white dark:bg-charleston-green-2"
></Input>
</div>
<div className="mt-6 relative px-3">
@@ -163,7 +162,7 @@ export default function ChunkModal({
handleSubmit(title, chunkText);
setModalState('INACTIVE');
}}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
>
Update
</button>

View File

@@ -59,7 +59,7 @@ export default function ConfigToolModal({
onChange={(e) => setAuthKey(e.target.value)}
borderVariant="thin"
placeholder={t('modals.configTool.apiKeyPlaceholder')}
label={t('modals.configTool.apiKeyLabel')}
labelBgClassName="bg-white dark:bg-charleston-green-2"
/>
</div>
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
@@ -67,7 +67,7 @@ export default function ConfigToolModal({
onClick={() => {
tool && handleAddTool(tool);
}}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
>
{t('modals.configTool.addButton')}
</button>

View File

@@ -11,6 +11,7 @@ export default function ConfirmationModal({
handleSubmit,
cancelLabel,
handleCancel,
variant = 'default',
}: {
message: string;
modalState: ActiveState;
@@ -19,8 +20,15 @@ export default function ConfirmationModal({
handleSubmit: () => void;
cancelLabel?: string;
handleCancel?: () => void;
variant?: 'default' | 'danger';
}) {
const { t } = useTranslation();
const submitButtonClasses =
variant === 'danger'
? 'rounded-3xl bg-rosso-corsa px-5 py-2 text-sm text-lotion transition-all hover:bg-red-2000 hover:font-bold tracking-[0.019em] hover:tracking-normal'
: 'rounded-3xl bg-purple-30 px-5 py-2 text-sm text-lotion transition-all hover:bg-violets-are-blue';
return (
<>
{modalState === 'ACTIVE' && (
@@ -39,7 +47,7 @@ export default function ConfirmationModal({
<div className="mt-6 flex flex-row-reverse gap-1">
<button
onClick={handleSubmit}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
className={submitButtonClasses}
>
{submitLabel}
</button>

View File

@@ -84,9 +84,10 @@ export default function CreateAPIKeyModal({
type="text"
className="rounded-md"
value={APIKeyName}
label={t('modals.createAPIKey.apiKeyName')}
placeholder={t('modals.createAPIKey.apiKeyName')}
onChange={(e) => setAPIKeyName(e.target.value)}
borderVariant="thin"
labelBgClassName="bg-white dark:bg-charleston-green-2"
></Input>
</div>
<div className="my-4">
@@ -144,7 +145,7 @@ export default function CreateAPIKeyModal({
createAPIKey(payload);
}
}}
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-violets-are-blue disabled:opacity-50"
>
{t('modals.createAPIKey.create')}
</button>

View File

@@ -42,6 +42,7 @@ export default function DeleteConvModal({
submitLabel={t('modals.deleteConv.delete')}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
variant="danger"
/>
);
}

View File

@@ -34,7 +34,7 @@ export default function SaveAPIKeyModal({
</span>
</div>
<button
className="my-1 h-10 w-20 rounded-full border border-solid border-purple-30 p-2 text-sm text-purple-30 hover:bg-purple-30 hover:text-white"
className="my-1 h-10 w-20 rounded-full border border-solid border-violets-are-blue p-2 text-sm text-violets-are-blue hover:bg-violets-are-blue hover:text-white"
onClick={handleCopyKey}
>
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}

View File

@@ -8,6 +8,7 @@ import {
selectPrompt,
} from '../preferences/preferenceSlice';
import Dropdown from '../components/Dropdown';
import ToggleSwitch from '../components/ToggleSwitch';
import { Doc } from '../models/misc';
import Spinner from '../assets/spinner.svg';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
@@ -101,38 +102,21 @@ export const ShareConversationModal = ({
return (
<WrapperModal close={close}>
<div className="flex flex-col gap-2">
<h2 className="text-xl font-medium text-eerie-black dark:text-white">
<h2 className="text-xl font-medium text-eerie-black dark:text-chinese-white">
{t('modals.shareConv.label')}
</h2>
<p className="text-sm text-eerie-black dark:text-white">
<p className="text-sm text-eerie-black dark:text-silver/60">
{t('modals.shareConv.note')}
</p>
<div className="flex items-center justify-between">
<span className="text-lg text-eerie-black dark:text-white">
{t('modals.shareConv.option')}
</span>
<label className="cursor-pointer select-none items-center">
<div className="relative">
<input
type="checkbox"
checked={allowPrompt}
onChange={togglePromptPermission}
className="sr-only"
/>
<div
className={`box block h-8 w-14 rounded-full border border-purple-30 ${
allowPrompt
? 'bg-purple-30 dark:bg-purple-30'
: 'dark:bg-transparent'
}`}
></div>
<div
className={`absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full transition ${
allowPrompt ? 'translate-x-full bg-silver' : 'bg-purple-30'
}`}
></div>
</div>
</label>
<ToggleSwitch
checked={allowPrompt}
onChange={togglePromptPermission}
size="medium"
/>
</div>
{allowPrompt && (
<div className="my-4">
@@ -149,19 +133,19 @@ export const ShareConversationModal = ({
</div>
)}
<div className="flex items-baseline justify-between gap-2">
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 py-3 px-4 text-eerie-black dark:text-white">
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 border-silver dark:border-silver/40 py-3 px-4 text-eerie-black dark:text-white">
{`${domain}/share/${identifier ?? '....'}`}
</span>
{status === 'fetched' ? (
<button
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-[#6F3FD1]"
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-violets-are-blue"
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)}
>
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
</button>
) : (
<button
className="my-1 flex h-10 w-28 items-center justify-evenly rounded-full border border-solid bg-purple-30 p-2 text-center text-sm font-normal text-white hover:bg-[#6F3FD1]"
className="my-1 flex h-10 w-28 items-center justify-evenly rounded-full bg-purple-30 p-2 text-center text-sm font-normal text-white hover:bg-violets-are-blue"
onClick={() => {
shareCoversationPublicly(allowPrompt);
}}

View File

@@ -32,16 +32,14 @@ function AddPrompt({
{t('modals.prompts.addDescription')}
</p>
<div>
<label htmlFor="new-prompt-name" className="sr-only">
Prompt Name
</label>
<Input
placeholder={t('modals.prompts.promptName')}
type="text"
label={t('modals.prompts.promptName')}
className="h-10 rounded-lg"
className="mb-4"
value={newPromptName}
onChange={(e) => setNewPromptName(e.target.value)}
labelBgClassName="bg-white dark:bg-[#26272E]"
borderVariant="thin"
/>
<div className="relative top-[7px] left-3">
<span className="bg-white px-1 text-xs text-silver dark:bg-[#26272E] dark:text-silver">
@@ -49,7 +47,7 @@ function AddPrompt({
</span>
</div>
<label htmlFor="new-prompt-content" className="sr-only">
Prompt Text
{t('modals.prompts.promptText')}
</label>
<textarea
id="new-prompt-content"
@@ -62,7 +60,7 @@ function AddPrompt({
<div className="mt-6 flex flex-row-reverse">
<button
onClick={handleAddPrompt}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:opacity-90"
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue disabled:hover:bg-purple-30"
disabled={disableSave}
title={
disableSave && newPromptName ? t('modals.prompts.nameExists') : ''
@@ -98,7 +96,7 @@ function EditPrompt({
return (
<div>
<div className="p-8">
<div className="">
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
{t('modals.prompts.editPrompt')}
</p>
@@ -106,28 +104,22 @@ function EditPrompt({
{t('modals.prompts.editDescription')}
</p>
<div>
<label htmlFor="edit-prompt-name" className="sr-only">
Prompt Name
</label>
<Input
placeholder={t('modals.prompts.promptName')}
type="text"
className="h-10 rounded-lg"
className="mb-4"
value={editPromptName}
onChange={(e) => setEditPromptName(e.target.value)}
labelBgClassName="bg-white dark:bg-charleston-green-2"
borderVariant="thin"
/>
<div className="relative bottom-12 left-3 mt-[-3.00px]">
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
{t('modals.prompts.promptName')}
</span>
</div>
<div className="relative top-[7px] left-3">
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
<span className="bg-white px-1 text-xs text-silver dark:bg-charleston-green-2 dark:text-silver">
{t('modals.prompts.promptText')}
</span>
</div>
<label htmlFor="edit-prompt-content" className="sr-only">
Prompt Text
{t('modals.prompts.promptText')}
</label>
<textarea
id="edit-prompt-content"
@@ -139,10 +131,10 @@ function EditPrompt({
</div>
<div className="mt-6 flex flex-row-reverse gap-4">
<button
className={`rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all ${
className={`rounded-3xl bg-purple-30 disabled:hover:bg-purple-30 hover:bg-violets-are-blue px-5 py-2 text-sm text-white transition-all ${
currentPromptEdit.type === 'public'
? 'cursor-not-allowed opacity-50'
: 'hover:opacity-90'
: ''
}`}
onClick={() => {
handleEditPrompt &&

View File

@@ -109,7 +109,7 @@ export default function APIKeys() {
<div className="mb-6 flex flex-col sm:flex-row justify-end items-start sm:items-center gap-3">
<button
onClick={() => setCreateModal(true)}
className="rounded-full w-full sm:w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
className="rounded-full text-sm w-[108px] h-[30px] bg-purple-30 text-white hover:bg-violets-are-blue flex items-center justify-center"
title={t('settings.apiKeys.createNew')}
>
{t('settings.apiKeys.createNew')}
@@ -122,13 +122,13 @@ export default function APIKeys() {
<table className="w-full table-auto">
<thead>
<tr className="border-b border-gray-300 dark:border-silver/40">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[35%]">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver w-[35%]">
{t('settings.apiKeys.name')}
</th>
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[35%]">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver w-[35%]">
{t('settings.apiKeys.sourceDoc')}
</th>
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[25%]">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver w-[25%]">
<span className="hidden sm:inline">
{t('settings.apiKeys.key')}
</span>
@@ -136,7 +136,7 @@ export default function APIKeys() {
{t('settings.apiKeys.key')}
</span>
</th>
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[5%]">
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] w-[5%]">
<span className="sr-only">Actions</span>
</th>
</tr>
@@ -160,7 +160,7 @@ export default function APIKeys() {
key={element.id}
className="group transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/50"
>
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[35%] min-w-48 max-w-0">
<td className="py-4 px-4 text-sm font-semibold text-gray-700 dark:text-[#E0E0E0] w-[35%] min-w-48 max-w-0">
<div className="truncate" title={element.name}>
{element.name}
</div>
@@ -222,6 +222,7 @@ export default function APIKeys() {
submitLabel={t('modals.deleteConv.delete')}
handleSubmit={() => handleDeleteKey(keyToDelete.id)}
handleCancel={() => setKeyToDelete(null)}
variant="danger"
/>
)}
</div>

View File

@@ -223,7 +223,7 @@ export default function Analytics() {
}
rounded="3xl"
border="border"
borderColor="gray-700"
darkBorderColor="dim-gray"
/>
</div>
)}
@@ -337,7 +337,7 @@ export default function Analytics() {
<div className="h-[345px] w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
<div className="flex flex-row items-center justify-start gap-3">
<p className="font-bold text-jet dark:text-bright-gray">
{t('settings.analytics.feedback')}
{t('settings.analytics.userFeedback')}
</p>
<Dropdown
size="w-[125px]"
@@ -450,5 +450,17 @@ function AnalyticsChart({
},
},
};
return <Bar options={options} plugins={[htmlLegendPlugin]} data={data} />;
return (
<Bar
options={options}
plugins={[htmlLegendPlugin]}
data={{
...data,
datasets: data.datasets.map((dataset) => ({
...dataset,
hoverBackgroundColor: `${dataset.backgroundColor}CC`, // 80% opacity
})),
}}
/>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
@@ -6,10 +6,11 @@ import userService from '../api/services/userService';
import ArrowLeft from '../assets/arrow-left.svg';
import caretSort from '../assets/caret-sort.svg';
import Edit from '../assets/edit.svg';
import EyeView from '../assets/eye-view.svg';
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import NoFilesIcon from '../assets/no-files.svg';
import SyncIcon from '../assets/sync.svg';
import Trash from '../assets/trash.svg';
import Trash from '../assets/red-trash.svg';
import Pagination from '../components/DocumentPagination';
import DropdownMenu from '../components/DropdownMenu';
import Input from '../components/Input';
@@ -27,6 +28,8 @@ import {
import Upload from '../upload/Upload';
import { formatDate } from '../utils/dateTimeUtils';
import { ChunkType } from './types';
import ContextMenu, { MenuOption } from '../components/ContextMenu';
import ThreeDots from '../assets/three-dots.svg';
const formatTokens = (tokens: number): string => {
const roundToTwoDecimals = (num: number): string => {
@@ -61,6 +64,53 @@ export default function Documents({
const [currentPage, setCurrentPage] = useState<number>(1);
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
const [totalPages, setTotalPages] = useState<number>(1);
const [activeMenuId, setActiveMenuId] = useState<string | null>(null);
const menuRefs = useRef<{ [key: string]: React.RefObject<HTMLDivElement> }>(
{},
);
// Create or get a ref for each document wrapper div (not the td)
const getMenuRef = (docId: string) => {
if (!menuRefs.current[docId]) {
menuRefs.current[docId] = React.createRef<HTMLDivElement>();
}
return menuRefs.current[docId];
};
const handleMenuClick = (e: React.MouseEvent, docId: string) => {
e.preventDefault();
e.stopPropagation();
const isAnyMenuOpen =
(syncMenuState.isOpen && syncMenuState.docId === docId) ||
activeMenuId === docId;
if (isAnyMenuOpen) {
setSyncMenuState((prev) => ({ ...prev, isOpen: false, docId: null }));
setActiveMenuId(null);
return;
}
setActiveMenuId(docId);
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (activeMenuId) {
const activeRef = menuRefs.current[activeMenuId];
if (
activeRef?.current &&
!activeRef.current.contains(event.target as Node)
) {
setActiveMenuId(null);
}
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [activeMenuId]);
const currentDocuments = paginatedDocuments ?? [];
const syncOptions = [
{ label: t('settings.documents.syncFrequency.never'), value: 'never' },
@@ -69,6 +119,16 @@ export default function Documents({
{ label: t('settings.documents.syncFrequency.monthly'), value: 'monthly' },
];
const [showDocumentChunks, setShowDocumentChunks] = useState<Doc>();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [syncMenuState, setSyncMenuState] = useState<{
isOpen: boolean;
docId: string | null;
document: Doc | null;
}>({
isOpen: false,
docId: null,
document: null,
});
const refreshDocs = useCallback(
(
@@ -164,6 +224,50 @@ export default function Documents({
}
};
const getActionOptions = (index: number, document: Doc): MenuOption[] => {
const actions: MenuOption[] = [
{
icon: EyeView,
label: t('settings.documents.view'),
onClick: () => {
setShowDocumentChunks(document);
},
iconWidth: 18,
iconHeight: 18,
variant: 'primary',
},
];
if (document.syncFrequency) {
actions.push({
icon: SyncIcon,
label: t('settings.documents.sync'),
onClick: () => {
setSyncMenuState({
isOpen: true,
docId: document.id ?? null,
document: document,
});
},
iconWidth: 14,
iconHeight: 14,
variant: 'primary',
});
}
actions.push({
icon: Trash,
label: t('convTile.delete'),
onClick: () => {
handleDeleteConfirmation(index, document);
},
iconWidth: 18,
iconHeight: 18,
variant: 'danger',
});
return actions;
};
useEffect(() => {
refreshDocs(undefined, 1, rowsPerPage);
}, [searchTerm]);
@@ -203,7 +307,7 @@ export default function Documents({
/>
</div>
<button
className="rounded-full w-full sm:w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
className="rounded-full w-[108px] h-[32px] text-sm bg-purple-30 text-white hover:bg-violets-are-blue flex items-center justify-center"
title={t('settings.documents.addNew')}
onClick={() => {
setIsOnboarding(false);
@@ -219,11 +323,11 @@ export default function Documents({
<table className="w-full table-auto">
<thead>
<tr className="border-b border-gray-300 dark:border-silver/40">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[45%]">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver w-[45%]">
{t('settings.documents.name')}
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[20%]">
<div className="flex justify-center items-center">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver w-[30%]">
<div className="flex justify-start items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer ml-2"
@@ -233,8 +337,8 @@ export default function Documents({
/>
</div>
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[25%]">
<div className="flex justify-center items-center">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver w-[15%]">
<div className="flex justify-start items-center">
<span className="hidden sm:inline">
{t('settings.documents.tokenUsage')}
</span>
@@ -249,10 +353,8 @@ export default function Documents({
/>
</div>
</th>
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[10%]">
<span className="sr-only">
{t('settings.documents.actions')}
</span>
<th className="py-3 px-4 sr-only w-[10%]">
{t('settings.documents.actions')}
</th>
</tr>
</thead>
@@ -269,61 +371,88 @@ export default function Documents({
</td>
</tr>
) : (
currentDocuments.map((document, index) => (
<tr
key={index}
className="group transition-colors cursor-pointer"
onClick={() => setShowDocumentChunks(document)}
>
<td
className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[45%] min-w-48 max-w-0 truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
title={document.name}
>
{document.name}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[20%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.date ? formatDate(document.date) : ''}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[25%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.tokens
? formatTokens(+document.tokens)
: ''}
</td>
<td
className="py-4 px-4 text-right w-[10%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
onClick={(e) => e.stopPropagation()} // Stop event propagation for the entire actions cell
>
<div className="flex items-center justify-end gap-3">
{!document.syncFrequency && (
<div className="w-8"></div>
)}
{document.syncFrequency && (
<DropdownMenu
name={t('settings.documents.sync')}
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
)}
<button
onClick={() => {
handleDeleteConfirmation(index, document);
}}
className="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0"
currentDocuments.map((document, index) => {
const docId = document.id ? document.id.toString() : '';
return (
<tr key={docId} className="group transition-colors">
<td
className="py-4 px-4 text-sm font-semibold text-gray-700 dark:text-[#E0E0E0] min-w-48 max-w-0 truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
title={document.name}
>
{document.name}
</td>
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.date ? formatDate(document.date) : ''}
</td>
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.tokens
? formatTokens(+document.tokens)
: ''}
</td>
<td
className="py-4 px-4 text-right group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
onClick={(e) => e.stopPropagation()}
>
<div
ref={getMenuRef(docId)}
className="flex items-center justify-end gap-3 relative"
>
<img
src={Trash}
alt={t('convTile.delete')}
className="h-4 w-4 opacity-60 hover:opacity-100"
{document.syncFrequency && (
<DropdownMenu
name={t('settings.documents.sync')}
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
isOpen={
syncMenuState.docId === docId &&
syncMenuState.isOpen
}
onOpenChange={(isOpen) => {
setSyncMenuState((prev) => ({
...prev,
isOpen,
docId: isOpen ? docId : null,
document: isOpen ? document : null,
}));
}}
anchorRef={getMenuRef(docId)}
position="bottom-left"
offset={{ x: 24, y: -24 }}
className="min-w-[120px]"
/>
)}
<button
onClick={(e) => handleMenuClick(e, docId)}
className="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0"
aria-label="Open menu"
data-testid={`menu-button-${docId}`}
>
<img
src={ThreeDots}
alt={t('convTile.menu')}
className="h-4 w-4 opacity-60 hover:opacity-100"
/>
</button>
<ContextMenu
isOpen={activeMenuId === docId}
setIsOpen={(isOpen) => {
setActiveMenuId(isOpen ? docId : null);
}}
options={getActionOptions(index, document)}
anchorRef={getMenuRef(docId)}
position="bottom-left"
offset={{ x: 48, y: -24 }}
className="z-50"
/>
</button>
</div>
</td>
</tr>
))
</div>
</td>
</tr>
);
})
)}
</tbody>
</table>
@@ -375,6 +504,7 @@ export default function Documents({
setDocumentToDelete(null);
}}
submitLabel={t('convTile.delete')}
variant="danger"
/>
)}
</div>
@@ -521,7 +651,7 @@ function DocumentChunks({
/>
</div>
<button
className="rounded-full w-full sm:w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
className="rounded-full w-[108px] h-[32px] text-sm bg-purple-30 text-white hover:bg-violets-are-blue flex items-center justify-center"
title={t('settings.documents.addNew')}
onClick={() => setAddModal('ACTIVE')}
>

View File

@@ -82,9 +82,11 @@ export default function General() {
changeLanguage(selectedLanguage?.value);
}, [selectedLanguage, changeLanguage]);
return (
<div className="mt-12">
<div className="mb-5">
<label className="block mb-2 font-bold text-jet dark:text-bright-gray">
<div className="mt-12 flex flex-col gap-4">
{' '}
<div className="flex flex-col gap-4">
{' '}
<label className="font-medium text-base text-jet dark:text-bright-gray">
{t('settings.general.selectTheme')}
</label>
<Dropdown
@@ -101,8 +103,8 @@ export default function General() {
border="border"
/>
</div>
<div className="mb-5">
<label className="block mb-2 font-bold text-jet dark:text-bright-gray">
<div className="flex flex-col gap-4">
<label className="font-medium text-base text-jet dark:text-bright-gray">
{t('settings.general.selectLanguage')}
</label>
<Dropdown
@@ -119,8 +121,8 @@ export default function General() {
border="border"
/>
</div>
<div className="mb-5">
<label className="block font-bold text-jet dark:text-bright-gray">
<div className="flex flex-col gap-4">
<label className="font-medium text-base text-jet dark:text-bright-gray">
{t('settings.general.chunks')}
</label>
<Dropdown
@@ -132,8 +134,8 @@ export default function General() {
border="border"
/>
</div>
<div className="mb-5">
<label className="mb-2 block font-bold text-jet dark:text-bright-gray">
<div className="flex flex-col gap-4">
<label className="font-medium text-base text-jet dark:text-bright-gray">
{t('settings.general.convHistory')}
</label>
<Dropdown
@@ -157,7 +159,7 @@ export default function General() {
border="border"
/>
</div>
<div className="mb-5">
<div className="flex flex-col gap-4">
<Prompts
prompts={prompts}
selectedPrompt={selectedPrompt}
@@ -167,12 +169,11 @@ export default function General() {
setPrompts={setPrompts}
/>
</div>
<div className="w-56">
<label className="block font-bold text-jet dark:text-bright-gray">
{t('settings.general.deleteAllLabel')}
</label>
<hr className="border-t w-[calc(min(665px,100%))] my-4 border-silver dark:border-silver/40" />
<div className="flex flex-col gap-2">
<button
className="mt-2 flex w-full cursor-pointer items-center justify-between rounded-3xl border border-solid border-red-700 px-5 py-3 text-red-700 transition-colors hover:bg-red-700 hover:text-white dark:border-red-600 dark:text-red-600 dark:hover:bg-red-600 dark:hover:text-white"
title={t('settings.general.deleteAllLabel')}
className="flex font-medium text-sm w-fit cursor-pointer items-center justify-between rounded-3xl border border-solid border-rosso-corsa bg-transparent px-5 py-3 text-rosso-corsa transition-colors hover:bg-rosso-corsa hover:text-white hover:font-bold tracking-[0.015em] hover:tracking-normal"
onClick={() => dispatch(setModalStateDeleteConv('ACTIVE'))}
>
{t('settings.general.deleteAllBtn')}

View File

@@ -104,6 +104,7 @@ export default function Logs() {
}
rounded="3xl"
border="border"
darkBorderColor="dim-gray"
/>
</div>
)}
@@ -121,46 +122,78 @@ type LogsTableProps = {
setPage: React.Dispatch<React.SetStateAction<number>>;
loading: boolean;
};
function LogsTable({ logs, setPage, loading }: LogsTableProps) {
const { t } = useTranslation();
const observerRef = useRef<any>();
const firstObserver = useCallback((node: HTMLDivElement) => {
if (observerRef.current) {
observerRef.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) setPage((prev) => prev + 1);
});
const observerRef = useRef<IntersectionObserver | null>(null);
const [openLogId, setOpenLogId] = useState<string | null>(null);
const handleLogToggle = (logId: string) => {
if (openLogId && openLogId !== logId) {
// If a different log is being opened, close the current one
const currentOpenLog = document.getElementById(
openLogId,
) as HTMLDetailsElement;
if (currentOpenLog) {
currentOpenLog.open = false;
}
}
if (node && observerRef.current) observerRef.current.observe(node);
setOpenLogId(logId);
};
const firstObserver = useCallback((node: HTMLDivElement | null) => {
if (observerRef.current) {
observerRef.current.disconnect();
}
if (!node) return;
observerRef.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setPage((prev) => prev + 1);
}
});
observerRef.current.observe(node);
}, []);
useEffect(() => {
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
}
};
}, []);
return (
<div className="logs-table border rounded-2xl h-[55vh] w-full overflow-hidden border-silver dark:border-silver/40">
<div className="h-8 bg-black/10 dark:bg-chinese-black flex flex-col items-start justify-center">
<div className="logs-table rounded-xl h-[55vh] w-full overflow-hidden bg-white dark:bg-black border border-light-silver dark:border-transparent">
<div className="h-8 bg-black/10 dark:bg-[#191919] flex flex-col items-start justify-center">
<p className="px-3 text-xs dark:text-gray-6000">
{t('settings.logs.tableHeader')}
</p>
</div>
<div
ref={observerRef}
className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-px"
>
<div className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-2 p-4">
{logs?.map((log, index) => {
if (index === logs.length - 1) {
return (
<div ref={firstObserver} key={index} className="w-full">
<Log log={log} />
<Log log={log} onToggle={handleLogToggle} />
</div>
);
} else return <Log key={index} log={log} />;
} else
return <Log key={index} log={log} onToggle={handleLogToggle} />;
})}
{loading && <SkeletonLoader component="logs" />}
</div>
</div>
);
}
function Log({ log }: { log: LogData }) {
function Log({
log,
onToggle,
}: {
log: LogData;
onToggle: (id: string) => void;
}) {
const { t } = useTranslation();
const logLevelColor = {
info: 'text-green-500',
@@ -168,9 +201,18 @@ function Log({ log }: { log: LogData }) {
warning: 'text-yellow-500',
};
const { id, action, timestamp, ...filteredLog } = log;
return (
<details className="group bg-transparent [&_summary::-webkit-details-marker]:hidden w-full hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal">
<summary className="flex flex-row items-start gap-2 text-gray-900 cursor-pointer p-2 group-open:bg-[#F9F9F9] dark:group-open:bg-dark-charcoal">
<details
id={log.id}
className="group bg-transparent [&_summary::-webkit-details-marker]:hidden w-full hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal rounded-xl group-open:opacity-80 [&[open]]:border [&[open]]:border-[#d9d9d9]"
onToggle={(e) => {
if ((e.target as HTMLDetailsElement).open) {
onToggle(log.id);
}
}}
>
<summary className="flex flex-row items-start gap-2 text-gray-900 cursor-pointer px-4 py-3 group-open:bg-[#F1F1F1] dark:group-open:bg-[#1B1B1B] group-open:rounded-t-xl p-2">
<img
src={ChevronRight}
alt="Expand log entry"
@@ -188,14 +230,15 @@ function Log({ log }: { log: LogData }) {
</h2>
</span>
</summary>
<div className="px-4 group-open:bg-[#F9F9F9] dark:group-open:bg-dark-charcoal">
<div className="px-4 py-3 group-open:bg-[#F1F1F1] dark:group-open:bg-[#1B1B1B] group-open:rounded-b-xl">
<p className="px-2 leading-relaxed text-gray-700 dark:text-gray-400 text-xs break-words">
{JSON.stringify(filteredLog, null, 2)}
</p>
<div className="my-px w-8">
<div className="my-px w-fit">
<CopyButton
text={JSON.stringify(filteredLog)}
colorLight="transparent"
showText={true}
/>
</div>
</div>

View File

@@ -35,10 +35,7 @@ export default function Prompts({
});
const [modalType, setModalType] = React.useState<'ADD' | 'EDIT'>('ADD');
const [modalState, setModalState] = React.useState<ActiveState>('INACTIVE');
const {
t,
i18n: { changeLanguage, language },
} = useTranslation();
const { t } = useTranslation();
const handleAddPrompt = async () => {
try {
@@ -135,11 +132,11 @@ export default function Prompts({
return (
<>
<div>
<div className="flex flex-row items-center gap-8">
<div>
<p className="font-semibold dark:text-bright-gray">
{t('settings.general.prompt')}
</p>
<div className="flex flex-col gap-4">
<p className="font-medium dark:text-bright-gray">
{t('settings.general.prompt')}
</p>
<div className="flex flex-row justify-start items-baseline gap-6">
<Dropdown
options={prompts}
selectedValue={selectedPrompt.name}
@@ -166,16 +163,17 @@ export default function Prompts({
}}
onDelete={handleDeletePrompt}
/>
<button
className="rounded-3xl w-20 h-10 text-sm border border-solid border-violets-are-blue text-violets-are-blue transition-colors hover:text-white hover:bg-violets-are-blue"
onClick={() => {
setModalType('ADD');
setModalState('ACTIVE');
}}
>
{t('settings.general.add')}
</button>
</div>
<button
className="mt-[24px] rounded-3xl border border-solid border-purple-30 px-5 py-3 text-purple-30 transition-colors hover:text-white hover:bg-[#6F3FD1] dark:border-purple-30 dark:text-purple-30 dark:hover:bg-purple-30 dark:hover:text-white"
onClick={() => {
setModalType('ADD');
setModalState('ACTIVE');
}}
>
{t('settings.general.addNew')}
</button>
</div>
</div>
<PromptsModal

View File

@@ -1,5 +1,4 @@
import React from 'react';
import userService from '../api/services/userService';
import ArrowLeft from '../assets/arrow-left.svg';
import CircleCheck from '../assets/circle-check.svg';
@@ -7,9 +6,11 @@ import CircleX from '../assets/circle-x.svg';
import Trash from '../assets/trash.svg';
import Dropdown from '../components/Dropdown';
import Input from '../components/Input';
import ToggleSwitch from '../components/ToggleSwitch';
import AddActionModal from '../modals/AddActionModal';
import { ActiveState } from '../models/misc';
import { APIActionType, APIToolType, UserToolType } from './types';
import { useTranslation } from 'react-i18next';
export default function ToolConfig({
tool,
@@ -25,7 +26,7 @@ export default function ToolConfig({
);
const [actionModalState, setActionModalState] =
React.useState<ActiveState>('INACTIVE');
const { t } = useTranslation();
const handleCheckboxChange = (actionIndex: number, property: string) => {
setTool({
...tool,
@@ -134,21 +135,18 @@ export default function ToolConfig({
{Object.keys(tool?.config).length !== 0 &&
tool.name !== 'api_tool' && (
<div className="relative w-96">
<span className="z-10 absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
API Key / Oauth
</span>
<Input
type="text"
value={authKey}
onChange={(e) => setAuthKey(e.target.value)}
borderVariant="thin"
placeholder="Enter API Key / Oauth"
></Input>
placeholder={t('modals.configTool.apiKeyPlaceholder')}
/>
</div>
)}
<div className="flex items-center gap-2">
<button
className="rounded-full px-5 py-[10px] bg-purple-30 text-white hover:bg-[#6F3FD1] text-nowrap text-sm"
className="rounded-full px-5 py-[10px] bg-purple-30 text-white hover:bg-violets-are-blue text-nowrap text-sm"
onClick={handleSaveChanges}
>
Save changes
@@ -172,7 +170,7 @@ export default function ToolConfig({
onClick={() => {
setActionModalState('ACTIVE');
}}
className="border border-solid border-purple-30 text-purple-30 dark:border-purple-30 dark:text-purple-30 transition-colors hover:bg-[#6F3FD1] hover:text-white dark:hover:bg-purple-30 dark:hover:text-white rounded-full text-sm px-5 py-1"
className="border border-solid border-violets-are-blue text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white rounded-full text-sm px-5 py-1"
>
Add action
</button>
@@ -192,33 +190,27 @@ export default function ToolConfig({
<p className="font-semibold text-eerie-black dark:text-bright-gray">
{action.name}
</p>
<label
htmlFor={`actionToggle-${actionIndex}`}
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
>
<input
type="checkbox"
id={`actionToggle-${actionIndex}`}
className="peer sr-only"
checked={action.active}
onChange={() => {
setTool({
...tool,
actions: tool.actions.map((act, index) => {
if (index === actionIndex) {
return { ...act, active: !act.active };
}
return act;
}),
});
}}
/>
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
</label>
<ToggleSwitch
checked={action.active}
onChange={(checked) => {
setTool({
...tool,
actions: tool.actions.map((act, index) => {
if (index === actionIndex) {
return { ...act, active: checked };
}
return act;
}),
});
}}
size="small"
id={`actionToggle-${actionIndex}`}
/>
</div>
<div className="mt-5 relative px-5 w-full sm:w-96">
<div className="mt-5 relative">
<Input
type="text"
className="w-[97%] ml-5"
placeholder="Enter description"
value={action.description}
onChange={(e) => {
@@ -236,7 +228,7 @@ export default function ToolConfig({
});
}}
borderVariant="thin"
></Input>
/>
</div>
<div className="px-5 py-4">
<table className="table-default">
@@ -431,19 +423,12 @@ function APIToolConfig({
<p className="font-semibold text-eerie-black dark:text-bright-gray">
{action.name}
</p>
<label
htmlFor={`actionToggle-${actionIndex}`}
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
>
<input
type="checkbox"
id={`actionToggle-${actionIndex}`}
className="peer sr-only"
checked={action.active}
onChange={() => handleActionToggle(actionName)}
/>
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
</label>
<ToggleSwitch
checked={action.active}
onChange={() => handleActionToggle(actionName)}
size="small"
id={`actionToggle-${actionIndex}`}
/>
</div>
<div className="mt-8 px-5">
<div className="relative w-full">
@@ -838,7 +823,7 @@ function APIActionTable({
<td colSpan={4} className="text-right">
<button
onClick={handleAddProperty}
className="bg-purple-30 text-white hover:bg-[#6F3FD1] rounded-full px-5 py-[4px] mr-1 text-sm"
className="bg-purple-30 text-white hover:bg-violets-are-blue rounded-full px-5 py-[4px] mr-1 text-sm"
>
{' '}
Add{' '}
@@ -865,7 +850,7 @@ function APIActionTable({
<td colSpan={5}>
<button
onClick={() => handleAddPropertyStart(section)}
className="flex items-start rounded-full px-5 py-[4px] border border-solid border-purple-30 text-purple-30 dark:border-purple-30 dark:text-purple-30 transition-colors hover:bg-[#6F3FD1] hover:text-white dark:hover:bg-purple-30 dark:hover:text-white text-nowrap text-sm"
className="flex items-start rounded-full px-5 py-[4px] border border-solid text-violets-are-blue border-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white text-nowrap text-sm"
>
Add New Field
</button>

View File

@@ -3,19 +3,16 @@ import { useTranslation } from 'react-i18next';
import userService from '../api/services/userService';
import CogwheelIcon from '../assets/cogwheel.svg';
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import NoFilesIcon from '../assets/no-files.svg';
import Input from '../components/Input';
import Spinner from '../components/Spinner';
import { useDarkTheme } from '../hooks';
import AddToolModal from '../modals/AddToolModal';
import { ActiveState } from '../models/misc';
import ToolConfig from './ToolConfig';
import { APIToolType, UserToolType } from './types';
import ToggleSwitch from '../components/ToggleSwitch';
export default function Tools() {
const { t } = useTranslation();
const [isDarkTheme] = useDarkTheme();
const [searchTerm, setSearchTerm] = React.useState('');
const [addToolModalState, setAddToolModalState] =
React.useState<ActiveState>('INACTIVE');
@@ -114,7 +111,7 @@ export default function Tools() {
/>
</div>
<button
className="rounded-full min-w-[160px] bg-purple-30 px-6 py-3 text-white hover:bg-[#6F3FD1] text-nowrap"
className="rounded-full w-[108px] h-[30px] text-sm bg-purple-30 text-white hover:bg-violets-are-blue flex items-center justify-center"
onClick={() => {
setAddToolModalState('ACTIVE');
}}
@@ -122,6 +119,7 @@ export default function Tools() {
{t('settings.tools.addTool')}
</button>
</div>
<div className="border-b border-light-silver dark:border-dim-gray mb-8 mt-5" />
{loading ? (
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
<div className="mt-24 h-32 col-span-2 lg:col-span-3 flex items-center justify-center">
@@ -129,90 +127,66 @@ export default function Tools() {
</div>
</div>
) : (
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
{userTools.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
).length === 0 ? (
<div className="mt-24 col-span-2 lg:col-span-3 text-center text-gray-500 dark:text-gray-400">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No tools found"
className="h-24 w-24 mx-auto mb-2"
/>
{t('settings.tools.noToolsFound')}
</div>
) : (
userTools
.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
)
.map((tool, index) => (
<div
key={index}
className="relative h-56 w-full p-6 border rounded-2xl border-silver dark:border-silver/40 flex flex-col justify-between"
<div className="flex flex-wrap gap-4 justify-center sm:justify-start">
{userTools
.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
)
.map((tool, index) => (
<div
key={index}
className="h-52 w-[300px] p-6 border rounded-2xl border-light-gainsboro dark:border-arsenic bg-white-3000 dark:bg-transparent flex flex-col justify-between relative"
>
<button
onClick={() => handleSettingsClick(tool)}
aria-label={t('settings.tools.configureToolAria', {
toolName: tool.displayName,
})}
className="absolute top-4 right-4"
>
<div className="w-full">
<div className="w-full flex items-center justify-between">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${tool.displayName} icon`}
className="h-8 w-8"
/>
<button
className="absolute top-3 right-3 cursor-pointer"
onClick={() => handleSettingsClick(tool)}
aria-label={t(
'settings.tools.configureToolAria',
{
toolName: tool.displayName,
},
)}
>
<img
src={CogwheelIcon}
alt={t('settings.tools.settingsIconAlt')}
className="h-[19px] w-[19px]"
/>
</button>
</div>
<div className="mt-[9px]">
<p className="text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed">
{tool.displayName}
</p>
<p className="mt-1 h-16 overflow-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed pr-1">
{tool.description}
</p>
</div>
<img
src={CogwheelIcon}
alt={t('settings.tools.settingsIconAlt')}
className="h-[19px] w-[19px]"
/>
</button>
<div className="w-full">
<div className="px-1 w-full flex items-center">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${tool.displayName} icon`}
className="h-6 w-6"
/>
</div>
<div className="absolute bottom-3 right-3">
<label
htmlFor={`toolToggle-${index}`}
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
<div className="mt-[9px]">
<p
title={tool.displayName}
className="px-1 text-[13px] font-semibold text-raisin-black-light dark:text-bright-gray leading-relaxed capitalize truncate"
>
<span className="sr-only">
{t('settings.tools.toggleToolAria', {
toolName: tool.displayName,
})}
</span>
<input
type="checkbox"
id={`toolToggle-${index}`}
className="peer sr-only"
checked={tool.status}
onChange={() =>
updateToolStatus(tool.id, !tool.status)
}
/>
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
</label>
{tool.displayName}
</p>
<p className="mt-1 px-1 h-24 overflow-auto text-[12px] text-old-silver dark:text-sonic-silver-light leading-relaxed">
{tool.description}
</p>
</div>
</div>
))
)}
<div className="absolute bottom-4 right-4">
<ToggleSwitch
checked={tool.status}
onChange={(checked) =>
updateToolStatus(tool.id, checked)
}
size="small"
id={`toolToggle-${index}`}
ariaLabel={t('settings.tools.toggleToolAria', {
toolName: tool.displayName,
})}
/>
</div>
</div>
))}
</div>
)}
</div>

View File

@@ -99,9 +99,9 @@ function Upload({
)
}
borderVariant="thin"
label={field.label}
required={isRequired}
colorVariant="silver"
labelBgClassName="bg-white dark:bg-charleston-green-2"
/>
);
case 'number':
@@ -121,9 +121,9 @@ function Upload({
)
}
borderVariant="thin"
label={field.label}
required={isRequired}
colorVariant="silver"
labelBgClassName="bg-white dark:bg-charleston-green-2"
/>
);
case 'enum':
@@ -609,7 +609,7 @@ function Upload({
onChange={(e) => setDocName(e.target.value)}
borderVariant="thin"
placeholder={t('modals.uploadDoc.name')}
label={t('modals.uploadDoc.name')}
labelBgClassName="bg-white dark:bg-charleston-green-2"
required={true}
/>
<div className="my-2" {...getRootProps()}>
@@ -656,6 +656,7 @@ function Upload({
handleIngestorTypeChange(selected.value as IngestorType)
}
size="w-full"
darkBorderColor="dim-gray"
rounded="3xl"
/>
{/* Dynamically render form fields based on schema */}
@@ -667,8 +668,8 @@ function Upload({
onChange={(e) => setRemoteName(e.target.value)}
borderVariant="thin"
placeholder="Name"
label="Name"
required={true}
labelBgClassName="bg-white dark:bg-charleston-green-2"
/>
{renderFormFields()}
{IngestorFormSchemas[ingestor.type].some(
@@ -707,7 +708,7 @@ function Upload({
className={`rounded-3xl px-4 py-2 font-medium text-[14px] ${
isUploadDisabled()
? 'cursor-not-allowed bg-gray-300 text-gray-500'
: 'cursor-pointer bg-purple-30 text-white hover:bg-purple-40'
: 'cursor-pointer bg-purple-30 text-white hover:bg-violets-are-blue'
}`}
>
{t('modals.uploadDoc.train')}

View File

@@ -11,7 +11,7 @@ module.exports = {
colors: {
'eerie-black': '#212121',
'black-1000': '#343541',
jet: '#343541',
'jet': '#343541',
'gray-alpha': 'rgba(0,0,0, .64)',
'gray-1000': '#F6F6F6',
'gray-2000': 'rgba(0, 0, 0, 0.5)',
@@ -32,23 +32,54 @@ module.exports = {
'green-2000': '#0FFF50',
'light-gray': '#edeef0',
'white-3000': '#ffffff',
'just-black':"#00000",
'purple-taupe':'#464152',
'just-black': '#00000',
'purple-taupe': '#464152',
'dove-gray': '#6c6c6c',
'silver': '#c4c4c4',
'rainy-gray': '#a4a4a4',
'raisin-black':'#222327',
'chinese-black':'#161616',
'chinese-silver':'#CDCDCD',
'dark-charcoal':'#2F3036',
'bright-gray':'#ECECF1',
'outer-space':'#444654',
'gun-metal':'#2E303E',
'sonic-silver':'#747474',
'soap':'#D8CCF1',
'independence':'#54546D',
'philippine-yellow':'#FFC700',
'bright-gray':'#EBEBEB'
'raisin-black': '#222327',
'chinese-black': '#161616',
'chinese-silver': '#CDCDCD',
'dark-charcoal': '#2F3036',
'bright-gray': '#ECECF1',
'outer-space': '#444654',
'gun-metal': '#2E303E',
'sonic-silver': '#747474',
'soap': '#D8CCF1',
'independence': '#54546D',
'philippine-yellow': '#FFC700',
'bright-gray': '#EBEBEB',
'chinese-white': '#e0e0e0',
'dark-gray': '#aaaaaa',
'dim-gray': '#6A6A6A',
'cultured': '#f4f4f4',
'charleston-green': '#2b2c31',
'charleston-green-2' : '#26272e',
'charleston-green-3':'#26272A',
'grey': '#7e7e7e',
'lotion': '#FBFBFB',
'platinum': '#e6e6e6',
'eerie-black-2': '#191919',
'light-silver': '#D9D9D9',
'carbon': '#2E2E2E',
'onyx':'#35363B',
'royal-purple': '#6C4AB0',
'chinese-black-2': '#0F1419',
'gainsboro': '#D9DCDE',
'onyx-2': '#35383C',
'philippine-grey': '#929292',
'charcoal-grey':'#53545D',
'rosso-corsa': '#D30000',
'north-texas-green': '#0C9D35',
'medium-purple': '#8d66dd',
'slate-blue': '#6f5fca',
'old-silver': '#848484',
'arsenic': '#4D4E58',
'light-gainsboro': '#d7D7D7',
'raisin-black-light': '#18181B',
'gunmetal': '#32333B',
'sonic-silver-light': '#7f7f82',
'violets-are-blue':'#976AF3'
},
},
},