Spaces:
Running
Running
/** | |
* | |
* Copyright 2023-2025 InspectorRAGet Team | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
**/ | |
'use client'; | |
import { isEmpty } from 'lodash'; | |
import { Button } from '@carbon/react'; | |
import { ChevronLeft, ChevronRight, Link } from '@carbon/icons-react'; | |
import ReactMarkdown from 'react-markdown'; | |
import remarkGfm from 'remark-gfm'; | |
import rehypeRaw from 'rehype-raw'; | |
import { ToolMessageDocument } from '@/src/types'; | |
import classes from './DocumentsViewer.module.scss'; | |
// =================================================================================== | |
// TYPES | |
// =================================================================================== | |
interface Props { | |
id: string; | |
documents: ToolMessageDocument[]; | |
documentIndex: number; | |
setDocumentIndex: Function; | |
onSelection?: Function; | |
} | |
// =================================================================================== | |
// RENDER FUNCTIONS | |
// =================================================================================== | |
function DocumentViewer({ | |
id, | |
document, | |
onSelection, | |
}: { | |
id: string; | |
document: ToolMessageDocument; | |
onSelection?: Function; | |
}) { | |
return ( | |
<div className={classes.document}> | |
<div className={classes.documentHeader}> | |
<div className={classes.documentToolbar}> | |
{document.url ? ( | |
<Button | |
kind="ghost" | |
renderIcon={Link} | |
iconDescription="Click to open link" | |
hasIconOnly | |
tooltipAlignment="end" | |
tooltipPosition="bottom" | |
onClick={() => { | |
window.open(document.url, '_blank'); | |
}} | |
></Button> | |
) : null} | |
</div> | |
</div> | |
<article | |
className={classes.documentContainer} | |
onMouseDown={() => { | |
if (onSelection) { | |
const [segment, documentIdx] = id.split('__documents--'); | |
onSelection( | |
`messages[${segment.split('message--').slice(-1)[0]}].documents[${documentIdx}].text`, | |
); | |
} | |
}} | |
onMouseUp={() => { | |
if (onSelection) { | |
const [segment, documentIdx] = id.split('__documents--'); | |
onSelection( | |
`messages[${segment.split('message--').slice(-1)[0]}].documents[${documentIdx}].text`, | |
); | |
} | |
}} | |
> | |
<div className={classes.markdown}> | |
<ReactMarkdown | |
remarkPlugins={[remarkGfm]} | |
rehypePlugins={[rehypeRaw]} | |
> | |
{document.text} | |
</ReactMarkdown> | |
</div> | |
</article> | |
</div> | |
); | |
} | |
// =================================================================================== | |
// MAIN FUNCTION | |
// =================================================================================== | |
export default function DocumentsViewer({ | |
id, | |
documents, | |
documentIndex, | |
setDocumentIndex, | |
onSelection, | |
}: Props) { | |
// Step 1: Render | |
if (isEmpty(documents)) { | |
return null; | |
} else { | |
return ( | |
<div className={classes.documentsViewer}> | |
{documents.length > 1 ? ( | |
<div className={classes.toolbar}> | |
<Button | |
id={'document--selector-prev'} | |
kind="ghost" | |
hasIconOnly | |
renderIcon={ChevronLeft} | |
iconDescription="Previous document" | |
onClick={() => { | |
if (documentIndex > 0) { | |
setDocumentIndex(documentIndex - 1); | |
} | |
}} | |
disabled={documentIndex === 0} | |
/> | |
<span className={classes.documentIndex}> | |
{documentIndex + 1} / {documents.length} | |
</span> | |
<Button | |
id={'document--selector-next'} | |
kind="ghost" | |
hasIconOnly | |
renderIcon={ChevronRight} | |
iconDescription="Next document" | |
onClick={() => { | |
if (documentIndex < documents.length - 1) { | |
setDocumentIndex(documentIndex + 1); | |
} | |
}} | |
disabled={documentIndex === documents.length - 1} | |
/> | |
</div> | |
) : null} | |
<div className={classes.container}> | |
<DocumentViewer | |
id={`${id}--${documentIndex}`} | |
document={documents[documentIndex]} | |
onSelection={onSelection} | |
/> | |
</div> | |
</div> | |
); | |
} | |
} | |