diff --git a/api/package.json b/api/package.json index 644c0ce7a9..19bad78cf8 100644 --- a/api/package.json +++ b/api/package.json @@ -49,7 +49,7 @@ "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.0", + "@librechat/agents": "^2.4.12", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", diff --git a/client/src/components/Chat/Input/Files/FileContainer.tsx b/client/src/components/Chat/Input/Files/FileContainer.tsx index 5131061f87..5a22fef256 100644 --- a/client/src/components/Chat/Input/Files/FileContainer.tsx +++ b/client/src/components/Chat/Input/Files/FileContainer.tsx @@ -1,22 +1,40 @@ import type { TFile } from 'librechat-data-provider'; import type { ExtendedFile } from '~/common'; +import { getFileType, cn } from '~/utils'; import FilePreview from './FilePreview'; import RemoveFile from './RemoveFile'; -import { getFileType } from '~/utils'; const FileContainer = ({ file, + overrideType, + buttonClassName, + containerClassName, onDelete, + onClick, }: { - file: ExtendedFile | TFile; + file: Partial; + overrideType?: string; + buttonClassName?: string; + containerClassName?: string; onDelete?: () => void; + onClick?: React.MouseEventHandler; }) => { - const fileType = getFileType(file.type); + const fileType = getFileType(overrideType ?? file.type); return ( -
-
-
+
+
+ {onDelete && }
); diff --git a/client/src/components/Chat/Input/Files/FilePreview.tsx b/client/src/components/Chat/Input/Files/FilePreview.tsx index 02851119af..31226ac65a 100644 --- a/client/src/components/Chat/Input/Files/FilePreview.tsx +++ b/client/src/components/Chat/Input/Files/FilePreview.tsx @@ -11,7 +11,7 @@ const FilePreview = ({ fileType, className = '', }: { - file?: ExtendedFile | TFile; + file?: Partial; fileType: { paths: React.FC; fill: string; diff --git a/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx b/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx index 8bc3192813..e1aeb86a5e 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx @@ -1,6 +1,27 @@ +import { memo } from 'react'; import { imageExtRegex } from 'librechat-data-provider'; import type { TAttachment, TFile, TAttachmentMetadata } from 'librechat-data-provider'; +import FileContainer from '~/components/Chat/Input/Files/FileContainer'; import Image from '~/components/Chat/Messages/Content/Image'; +import { useAttachmentLink } from './LogLink'; + +const FileAttachment = memo(({ attachment }: { attachment: TAttachment }) => { + const { handleDownload } = useAttachmentLink({ + href: attachment.filepath, + filename: attachment.filename, + }); + const extension = attachment.filename.split('.').pop(); + + return ( + + ); +}); export default function Attachment({ attachment }: { attachment?: TAttachment }) { if (!attachment) { @@ -21,5 +42,5 @@ export default function Attachment({ attachment }: { attachment?: TAttachment }) /> ); } - return null; + return ; } diff --git a/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx b/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx index 93fdab434e..40ad8c6a66 100644 --- a/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx @@ -7,7 +7,7 @@ import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite'; import { useProgress, useLocalize } from '~/hooks'; import { CodeInProgress } from './CodeProgress'; import Attachment from './Attachment'; -import LogContent from './LogContent'; +import Stdout from './Stdout'; import store from '~/store'; interface ParsedArgs { @@ -17,8 +17,17 @@ interface ParsedArgs { export function useParseArgs(args: string): ParsedArgs { return useMemo(() => { + let parsedArgs: ParsedArgs | string = args; + try { + parsedArgs = JSON.parse(args); + } catch { + // console.error('Failed to parse args:', e); + } + if (typeof parsedArgs === 'object') { + return parsedArgs; + } const langMatch = args.match(/"lang"\s*:\s*"(\w+)"/); - const codeMatch = args.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"args"|$)/s); + const codeMatch = args.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"(session_id|args)"|"\s*})/s); let code = ''; if (codeMatch) { @@ -26,7 +35,7 @@ export function useParseArgs(args: string): ParsedArgs { if (code.endsWith('"}')) { code = code.slice(0, -2); } - code = code.replace(/\\n/g, '\n').replace(/\\/g, ''); + code = code.replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\'); } return { @@ -99,15 +108,17 @@ export default function ExecuteCode({ color: 'white', }} > -
-                  
-                
+
)} )} - {attachments?.map((attachment, index) => )} +
+ {attachments?.map((attachment, index) => ( + + ))} +
); } diff --git a/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx b/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx index c1886e7460..590b6d7b8a 100644 --- a/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx @@ -8,11 +8,11 @@ interface LogLinkProps { children: React.ReactNode; } -const LogLink: React.FC = ({ href, filename, children }) => { +export const useAttachmentLink = ({ href, filename }: Pick) => { const { showToast } = useToastContext(); const { refetch: downloadFile } = useCodeOutputDownload(href); - const handleDownload = async (event: React.MouseEvent) => { + const handleDownload = async (event: React.MouseEvent) => { event.preventDefault(); try { const stream = await downloadFile(); @@ -36,6 +36,11 @@ const LogLink: React.FC = ({ href, filename, children }) => { } }; + return { handleDownload }; +}; + +const LogLink: React.FC = ({ href, filename, children }) => { + const { handleDownload } = useAttachmentLink({ href, filename }); return ( = ({ output = '' }) => { + const processedContent = useMemo(() => { + if (!output) { + return ''; + } + + const parts = output.split('Generated files:'); + return parts[0].trim(); + }, [output]); + + return ( + processedContent && ( +
+        
{processedContent}
+
+ ) + ); +}; + +export default Stdout; diff --git a/client/src/components/svg/Files/FileIcon.tsx b/client/src/components/svg/Files/FileIcon.tsx index 559121efbd..788615171e 100644 --- a/client/src/components/svg/Files/FileIcon.tsx +++ b/client/src/components/svg/Files/FileIcon.tsx @@ -5,7 +5,7 @@ export default function FileIcon({ file, fileType, }: { - file?: ExtendedFile | TFile; + file?: Partial; fileType: { fill: string; paths: React.FC; diff --git a/client/src/hooks/Input/useAutoSave.ts b/client/src/hooks/Input/useAutoSave.ts index bebf54af3f..233a175a53 100644 --- a/client/src/hooks/Input/useAutoSave.ts +++ b/client/src/hooks/Input/useAutoSave.ts @@ -128,7 +128,7 @@ export const useAutoSave = ({ const handleInput = debounce((e: React.ChangeEvent) => { const value = e.target.value; - if (value) { + if (value && value.length > 1) { localStorage.setItem( `${LocalStorageKeys.TEXT_DRAFT}${conversationId}`, encodeBase64(value), diff --git a/client/src/utils/files.ts b/client/src/utils/files.ts index 337d1cc84f..3ca07e51d0 100644 --- a/client/src/utils/files.ts +++ b/client/src/utils/files.ts @@ -48,6 +48,7 @@ export const fileTypes = { title: 'File', }, text: textDocument, + txt: textDocument, // application:, /* Partial matches */ diff --git a/package-lock.json b/package-lock.json index 17e53223c3..158ec0e959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.0", + "@librechat/agents": "^2.4.12", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -17629,9 +17629,9 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.62.tgz", - "integrity": "sha512-JJiQwjV5/uVtwiVH/lt+QXhuh0nGhylZSLkMQXc1923TBUC4SHwU0JIKEDqh820PlGNkUu0nODJSAzS/6zPRtQ==", + "version": "0.0.65", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.65.tgz", + "integrity": "sha512-Zn1FhiKr/mYa1+W5NcuCPWmdTtJS4UZYu+YVEjxgESd0aMX19FTkqjaSV6tFDcRqHHwlIgHloCSqHLkYWF/Zug==", "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", @@ -17819,9 +17819,9 @@ } }, "node_modules/@librechat/agents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.0.tgz", - "integrity": "sha512-ijPJw+lMMPJ+Y66xSbh0cCiuODihl0TET3CWAxZweVGqynYqtL8PvPqlxtw+jftmrLjDFV04UM2NiDbzDb87HA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.12.tgz", + "integrity": "sha512-m8CEVCjVeQDKXMS0ISG4h4YXU1x51yWGPLCuiNbQI3k+fmDvEFqVuzFi15OhVfoGGQxbKfzGiiF8fsKK2xIKEw==", "dependencies": { "@langchain/anthropic": "^0.3.16", "@langchain/aws": "^0.1.7", diff --git a/packages/data-provider/src/file-config.ts b/packages/data-provider/src/file-config.ts index c380dacf6b..3798b48d4d 100644 --- a/packages/data-provider/src/file-config.ts +++ b/packages/data-provider/src/file-config.ts @@ -112,7 +112,7 @@ export const excelMimeTypes = /^application\/(vnd\.ms-excel|msexcel|x-msexcel|x-ms-excel|x-excel|x-dos_ms_excel|xls|x-xls|vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet)$/; export const textMimeTypes = - /^(text\/(x-c|x-csharp|x-c\+\+|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv))$/; + /^(text\/(x-c|x-csharp|tab-separated-values|x-c\+\+|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv))$/; export const applicationMimeTypes = /^(application\/(epub\+zip|csv|json|pdf|x-tar|typescript|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation|spreadsheetml\.sheet)|xml|zip))$/; @@ -152,6 +152,7 @@ export const codeTypeMapping: { [key: string]: string } = { yml: 'application/x-yaml', yaml: 'application/x-yaml', log: 'text/plain', + tsv: 'text/tab-separated-values', }; export const retrievalMimeTypes = [ @@ -230,7 +231,7 @@ export const convertStringsToRegex = (patterns: string[]): RegExp[] => const regex = new RegExp(pattern); acc.push(regex); } catch (error) { - console.error(`Invalid regex pattern "${pattern}" skipped.`); + console.error(`Invalid regex pattern "${pattern}" skipped.`, error); } return acc; }, []);