// SPDX-License-Identifier: MIT // Copyright contributors to the openassistant project import { ToolCallComponents, StreamMessagePart } from '@openassistant/core'; import { ToolInvocation } from 'ai'; import React, { ReactNode, memo } from 'react'; import remarkGfm from 'remark-gfm'; import Markdown from 'react-markdown'; import { Accordion, AccordionItem, Table, TableBody, TableCell, TableRow, TableHeader, TableColumn, Card, CardBody, } from '@heroui/react'; import { Icon } from '@iconify/react'; export const MarkdownContent = ({ text, showMarkdown = true, }: { text?: string; showMarkdown?: boolean; }) => { if (!showMarkdown) { return (
{text}
); } return (
( ), ol: ({ children }) => (
    {children}
), li: ({ children }) => ( // Used Tailwind's CSS nesting syntax to target p tags directly inside li elements with [&>p] // Added !mt-0 to force margin-top to 0 // Used -translate-y-5 to move the paragraph up by 1.25rem to align with the marker // Added negative margin bottom to compensate for the translated paragraph
  • {children}
  • ), p: ({ children }) => (

    {children}

    ), }} > {text}
    ); }; class ToolCallErrorBoundary extends React.Component< { children: ReactNode; onError?: () => void }, { hasError: boolean } > { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: Error) { console.error('Tool call component error:', error); this.props.onError?.(); } render() { if (this.state.hasError) { return (
    Failed to render tool component.
    ); } return this.props.children; } } const ToolCallComponentRenderer = memo( function ToolCallComponentRenderer({ Component, additionalData, }: { Component: | React.ComponentType> | React.ReactElement | null; additionalData: unknown; toolCallId: string; }) { if (!Component) return null; return ( {typeof Component === 'function' ? ( )} /> ) : ( Component )} ); }, (prevProps, nextProps) => { // comparison of additionalData using toolCallId return prevProps.toolCallId === nextProps.toolCallId; } ); export function PartComponent({ part, components, useMarkdown, showTools, }: { part: StreamMessagePart; components?: ToolCallComponents; useMarkdown?: boolean; showTools?: boolean; }) { if (part.type === 'text') { return (
    {useMarkdown ? : part.text}
    ); } else if (part.type === 'tool-invocation' && showTools) { return ( <> ); } else { return <>This part is not supported: {part.type}; } } export function ToolCallComponent({ toolInvocation, additionalData, components, }: { toolInvocation: ToolInvocation; additionalData: unknown; components?: ToolCallComponents; }) { const { toolName, args, state } = toolInvocation; const isCompleted = state === 'result'; const llmResult = state === 'result' ? toolInvocation.result : {}; const Component = components?.find( (component) => component.toolName === toolName )?.component as React.ComponentType> | undefined; const toolSuccess = Boolean(llmResult?.success); const tableItems = llmResult ? Object.entries(llmResult).map(([key, value]) => ({ key, value: typeof value === 'object' ? JSON.stringify(value, (_, v) => typeof v === 'bigint' ? v.toString() : v ) : String(value), })) : []; return (
    ${toolName}`} startContent={ !isCompleted && ( ) } >
    function call:
    {toolName} ( {Object.entries(args).map(([key, value], index, array) => ( {key} : {typeof value === 'object' && value !== null ? JSON.stringify(value, (_, v) => typeof v === 'bigint' ? v.toString() : v ) : String(value)} {index < array.length - 1 && ( , )} ))} )
    {llmResult && (
    result:
    Key Value {(item) => ( {item.key} {item.value} )}
    )}
    {Component && isCompleted && toolSuccess && (
    )}
    ); }