{children}
diff --git a/frontend/src/components/CodeSnippet.tsx b/frontend/src/components/CodeSnippet.tsx index 4bb900173a..8cd9273711 100644 --- a/frontend/src/components/CodeSnippet.tsx +++ b/frontend/src/components/CodeSnippet.tsx @@ -26,7 +26,7 @@ const HighlightedCode = ({ language, children }: CodeSnippetProps) => { }, []); return ( -
+
{
const [copied, setCopied] = useState(false);
const { t } = useTranslation();
+ // Function to extract the image URL from the content
+ const extractImageUrl = (text: string): string | null => {
+ const regex = /!\[.*?\]\((https?:\/\/[^\s)]+)\)/;
+ const match = text.match(regex);
+ if (match) {
+ const url = match[1];
+ return url.includes('?type=image') ? url : null;
+ }
+ return null;
+ };
+
+ // Memoized image URL extraction
+ const imageUrl = useMemo(() => extractImageUrl(content), [content]);
+
const copyToClipboard = async () => {
try {
- const textToCopy =
- typeof content === 'object'
- ? JSON.stringify(content, null, 2)
- : String(content);
-
- await navigator.clipboard.writeText(textToCopy);
+ await navigator.clipboard.writeText(content);
setCopied(true);
// Reset copied state after 2 seconds
- setTimeout(() => {
- setCopied(false);
- }, 2000);
+ setTimeout(() => setCopied(false), 2000);
} catch (err) {
toast.error('Failed to copy: ' + String(err));
}
};
+ const handleDownload = () => {
+ if (!imageUrl) {
+ toast.error('No file URL found');
+ return;
+ }
+ const a = document.createElement('a');
+ a.href = imageUrl;
+ a.download = 'downloaded-image';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+
return (
-
-
-
-
-
-
- {copied
- ? t('chat.messages.actions.copy.success')
- : t('chat.messages.actions.copy.button')}
-
-
-
+
+
+
+
+
+
+
+ {copied
+ ? t('chat.messages.actions.copy.success')
+ : t('chat.messages.actions.copy.button')}
+
+
+
+
+ {imageUrl && (
+
+
+
+
+
+ {t('chat.messages.actions.download.button')}
+
+
+ )}
+
);
};
diff --git a/frontend/src/components/Elements/Audio.tsx b/frontend/src/components/Elements/Audio.tsx
index 8e06e6d41c..b2b49b60de 100644
--- a/frontend/src/components/Elements/Audio.tsx
+++ b/frontend/src/components/Elements/Audio.tsx
@@ -1,16 +1,47 @@
import { cn } from '@/lib/utils';
+import { useEffect, useState } from 'react';
import { IAudioElement } from '@chainlit/react-client';
const AudioElement = ({ element }: { element: IAudioElement }) => {
- if (!element.url) {
- return null;
- }
+ const [audioSrc, setAudioSrc] = useState(null);
+
+ useEffect(() => {
+ if (!element.url) return;
+
+ const fetchAudio = async () => {
+ const token = localStorage.getItem('chainlit_token');
+ const headers: Record = {
+ ...(token ? { Authorization: `Bearer ${token}` } : {})
+ };
+ try {
+ const res = await fetch(element.url, {
+ headers,
+ credentials: 'include',
+ mode: 'cors'
+ });
+
+ if (!res.ok) throw new Error('Failed to fetch audio');
+
+ const blob = await res.blob();
+ const url = URL.createObjectURL(blob);
+ setAudioSrc(url);
+
+ return () => URL.revokeObjectURL(url);
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ fetchAudio();
+ }, [element.url]);
+
+ if (!audioSrc) return null;
return (
{element.name}
-
+
);
};
diff --git a/frontend/src/components/FilePickerDialog.tsx b/frontend/src/components/FilePickerDialog.tsx
new file mode 100644
index 0000000000..62a0b18482
--- /dev/null
+++ b/frontend/src/components/FilePickerDialog.tsx
@@ -0,0 +1,55 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import { Translator } from '@/components/i18n';
+import { Button } from './ui/button';
+import FilePicker from '@evoya/file-picker/src/components/FilePicker';
+import { EvoyaFile } from '@evoya/file-picker/src/types';
+import { FilePickerContext } from '@evoya/file-picker/src/context/file-context';
+
+interface Props {
+ open: boolean;
+ setOpen: (val: boolean) => void;
+ selectFile: (val: EvoyaFile) => void
+}
+
+const FilePickerDialog = ({ open, setOpen, selectFile }: Props): JSX.Element => {
+ return (
+
+ );
+};
+
+export { FilePickerDialog };
diff --git a/frontend/src/components/LeftSidebar/Search.tsx b/frontend/src/components/LeftSidebar/Search.tsx
index 4e1ef30790..a223461703 100644
--- a/frontend/src/components/LeftSidebar/Search.tsx
+++ b/frontend/src/components/LeftSidebar/Search.tsx
@@ -38,25 +38,6 @@ export default function SearchChats() {
const apiClient = useContext(ChainlitContext);
- // Debounced search function
- const debouncedSearch = useMemo(
- () =>
- _.debounce(async (query: string) => {
- setLoading(true);
- try {
- const { data } = await apiClient.listThreads(
- { first: 20, cursor: undefined },
- { search: query || undefined }
- );
- setThreads(data || []);
- } catch (error) {
- toast.error('Error fetching threads: ' + error);
- } finally {
- setLoading(false);
- }
- }, 300),
- [apiClient]
- );
// Group threads by month and year
const groupedThreads = useMemo(() => {
@@ -80,12 +61,6 @@ export default function SearchChats() {
return () => document.removeEventListener('keydown', down);
}, []);
- useEffect(() => {
- debouncedSearch(searchQuery);
- return () => {
- debouncedSearch.cancel();
- };
- }, [searchQuery, debouncedSearch]);
return (
<>
diff --git a/frontend/src/components/Markdown.tsx b/frontend/src/components/Markdown.tsx
index aa72ef6b51..124822826c 100644
--- a/frontend/src/components/Markdown.tsx
+++ b/frontend/src/components/Markdown.tsx
@@ -1,8 +1,9 @@
import { cn } from '@/lib/utils';
import { omit } from 'lodash';
-import { useContext, useMemo } from 'react';
+import { isValidElement, useContext, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { PluggableList } from 'react-markdown/lib';
+import { VegaLite } from 'react-vega';
import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import remarkDirective from 'remark-directive';
@@ -10,6 +11,7 @@ import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import { visit } from 'unist-util-visit';
+import ResponseTextItem from '@chainlit/copilot/src/evoya/privacyShield/ResponseTextItem';
import { ChainlitContext, type IMessageElement } from '@chainlit/react-client';
import { AspectRatio } from '@/components/ui/aspect-ratio';
@@ -28,6 +30,8 @@ import BlinkingCursor from './BlinkingCursor';
import CodeSnippet from './CodeSnippet';
import { ElementRef } from './Elements/ElementRef';
import { MarkdownAlert, alertComponents } from './MarkdownAlert';
+import { MermaidDiagram } from './Mermaid';
+import Step from './chat/Messages/Message/Step';
interface Props {
allowHtml?: boolean;
@@ -83,6 +87,26 @@ const cursorPlugin = () => {
};
};
+const fixDirectiveColonPlugin = () => {
+ return (tree: any) => {
+ visit(tree, (node: any, index: number, parent: any) => {
+ if (
+ (node.type === 'textDirective' ||
+ node.type === 'leafDirective' ||
+ node.type === 'containerDirective') &&
+ !node.data?.hName &&
+ /^[a-zA-Z0-9_-]+$/.test(node.name)
+ ) {
+ const directiveText = `:${node.name}`;
+ parent.children.splice(index, 1, {
+ type: 'text',
+ value: directiveText
+ });
+ }
+ });
+ };
+};
+
const Markdown = ({
allowHtml,
latex,
@@ -90,6 +114,7 @@ const Markdown = ({
className,
children
}: Props) => {
+ const rawContent = children;
const apiClient = useContext(ChainlitContext);
const rehypePlugins = useMemo(() => {
@@ -98,7 +123,10 @@ const Markdown = ({
rehypePlugins = [rehypeRaw as any, ...rehypePlugins];
}
if (latex) {
- rehypePlugins = [rehypeKatex as any, ...rehypePlugins];
+ rehypePlugins = [
+ [rehypeKatex as any, { output: 'mathml' }],
+ ...rehypePlugins
+ ];
}
return rehypePlugins;
}, [allowHtml, latex]);
@@ -108,6 +136,7 @@ const Markdown = ({
cursorPlugin,
remarkGfm as any,
remarkDirective as any,
+ fixDirectiveColonPlugin,
MarkdownAlert
];
@@ -123,7 +152,17 @@ const Markdown = ({
remarkPlugins={remarkPlugins}
rehypePlugins={rehypePlugins}
components={{
- ...alertComponents, // add alert components
+ ...alertComponents,
+ span({ children, ...props }) {
+ if (props.node?.properties.dataPrivacyComponent) {
+ return (
+
+ );
+ }
+ return {children};
+ },
code(props) {
return (
+
+ {diagramText}
+
+ {evoya?.additionalInfo?.text ? (
+ evoya?.additionalInfo?.text
+ ) : (
+
Commands
++ {displayedCommand?.prompt_content || + 'Select a command to view its content'} +
++ {filteredCommands[selectedIndex]?.description || + 'Select a command to view its content'} +
+
+ {filteredAgents[selectedIndex]?.description || (
+
+
+
-
+
+
+
+
+
- {children} + {renderContent}
+ {stepInfo} +
+a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...props} + /> + ) +} + +function ItemActions({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( + + ) +} + +function ItemFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( + + ) +} + +export { + Item, + ItemMedia, + ItemContent, + ItemActions, + ItemGroup, + ItemSeparator, + ItemTitle, + ItemDescription, + ItemHeader, + ItemFooter, +} diff --git a/frontend/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx index c02dc8ad38..5ba8b1e54a 100644 --- a/frontend/src/components/ui/popover.tsx +++ b/frontend/src/components/ui/popover.tsx @@ -20,7 +20,7 @@ const PopoverContent = React.forwardRef< align={align} sideOffset={sideOffset} className={cn( - 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + 'z-50 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className )} {...props} diff --git a/frontend/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable.tsx index 5bd02778e3..d13828be20 100644 --- a/frontend/src/components/ui/resizable.tsx +++ b/frontend/src/components/ui/resizable.tsx @@ -26,7 +26,7 @@ const ResizableHandle = ({ }) => (
+
+
+
+ {sessionUuid === '' ? (
+
+ {sessionUuid === '' ? (
+
+ {value}
+
+ ) : (
+ {value || '-'}
+ );
+ }
+
+ if (typeof value === 'boolean') {
+ return {value ? 'Yes' : 'No'};
+ }
+
+ if (typeof value === 'number') {
+ return {value};
+ }
+
+ if (value === null) {
+ return -;
+ }
+
+ if (value === undefined) {
+ return -;
+ }
+
+ return {String(value)};
+};
+
+const shouldHideField = (label?: string, sectionLabel?: string) =>
+ label === 'parameters' && sectionLabel === 'function';
+
+const ReadableValue = ({ label, value, depth = 0 }: ReadableValueProps) => {
+ if (Array.isArray(value)) {
+ return (
+