Feature/Code Interpreter (#3183)

* Base changes for ServerSide Events (instead of socket.io)

* lint fixes

* adding of interface and separate methods for streaming events

* lint

* first draft, handles both internal and external prediction end points.

* lint fixes

* additional internal end point for streaming and associated changes

* return streamresponse as true to build agent flow

* 1) JSON formatting for internal events
2) other fixes

* 1) convert internal event to metadata to maintain consistency with external response

* fix action and metadata streaming

* fix for error when agent flow is aborted

* prevent subflows from streaming and other code cleanup

* prevent streaming from enclosed tools

* add fix for preventing chaintool streaming

* update lock file

* add open when hidden to sse

* Streaming errors

* Streaming errors

* add fix for showing error message

* add code interpreter

* add artifacts to view message dialog

* Update pnpm-lock.yaml

---------

Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in>
This commit is contained in:
Henry Heng
2024-09-17 08:44:56 +01:00
committed by GitHub
parent 26444ac3ae
commit b02f279e9d
21 changed files with 729 additions and 333 deletions
@@ -8,6 +8,7 @@ import rehypeRaw from 'rehype-raw'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import axios from 'axios'
import { cloneDeep } from 'lodash'
// material-ui
import {
@@ -207,7 +208,16 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
if (chatmsg.fileAnnotations) msg.fileAnnotations = chatmsg.fileAnnotations
if (chatmsg.feedback) msg.feedback = chatmsg.feedback?.content
if (chatmsg.agentReasoning) msg.agentReasoning = chatmsg.agentReasoning
if (chatmsg.artifacts) {
obj.artifacts = chatmsg.artifacts
obj.artifacts.forEach((artifact) => {
if (artifact.type === 'png' || artifact.type === 'jpeg') {
artifact.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatmsg.chatflowid}&chatId=${
chatmsg.chatId
}&fileName=${artifact.data.replace('FILE-STORAGE::', '')}`
}
})
}
if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) {
obj[chatPK] = {
id: chatmsg.chatId,
@@ -341,7 +351,16 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
if (chatmsg.usedTools) obj.usedTools = chatmsg.usedTools
if (chatmsg.fileAnnotations) obj.fileAnnotations = chatmsg.fileAnnotations
if (chatmsg.agentReasoning) obj.agentReasoning = chatmsg.agentReasoning
if (chatmsg.artifacts) {
obj.artifacts = chatmsg.artifacts
obj.artifacts.forEach((artifact) => {
if (artifact.type === 'png' || artifact.type === 'jpeg') {
artifact.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatmsg.chatflowid}&chatId=${
chatmsg.chatId
}&fileName=${artifact.data.replace('FILE-STORAGE::', '')}`
}
})
}
loadedMessages.push(obj)
}
setChatMessages(loadedMessages)
@@ -574,6 +593,83 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [feedbackTypeFilter])
const agentReasoningArtifacts = (artifacts) => {
const newArtifacts = cloneDeep(artifacts)
for (let i = 0; i < newArtifacts.length; i++) {
const artifact = newArtifacts[i]
if (artifact && (artifact.type === 'png' || artifact.type === 'jpeg')) {
const data = artifact.data
newArtifacts[i].data = `${baseURL}/api/v1/get-upload-file?chatflowId=${
dialogProps.chatflow.id
}&chatId=${selectedChatId}&fileName=${data.replace('FILE-STORAGE::', '')}`
}
}
return newArtifacts
}
const renderArtifacts = (item, index, isAgentReasoning) => {
if (item.type === 'png' || item.type === 'jpeg') {
return (
<Card
key={index}
sx={{
p: 0,
m: 0,
mt: 2,
mb: 2,
flex: '0 0 auto'
}}
>
<CardMedia
component='img'
image={item.data}
sx={{ height: 'auto' }}
alt={'artifact'}
style={{
width: isAgentReasoning ? '200px' : '100%',
height: isAgentReasoning ? '200px' : 'auto',
objectFit: 'cover'
}}
/>
</Card>
)
} else if (item.type === 'html') {
return (
<div style={{ marginTop: '20px' }}>
<div dangerouslySetInnerHTML={{ __html: item.data }}></div>
</div>
)
} else {
return (
<MemoizedReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax, rehypeRaw]}
components={{
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<CodeBlock
key={Math.random()}
chatflowid={dialogProps.chatflow.id}
isDialog={true}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}
>
{item.data}
</MemoizedReactMarkdown>
)
}
}
const component = show ? (
<Dialog
onClose={onCancel}
@@ -879,24 +975,6 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
width: '100%'
}}
>
{message.usedTools && (
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
{message.usedTools.map((tool, index) => {
return (
<Chip
size='small'
key={index}
label={tool.tool}
component='a'
sx={{ mr: 1, mt: 1 }}
variant='outlined'
clickable
onClick={() => onSourceDialogClick(tool, 'Used Tools')}
/>
)
})}
</div>
)}
{message.fileUploads && message.fileUploads.length > 0 && (
<div
style={{
@@ -1008,6 +1086,31 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
/>
</div>
)}
{agent.artifacts && (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
width: '100%',
gap: '8px'
}}
>
{agentReasoningArtifacts(
agent.artifacts
).map((item, index) => {
return item !== null ? (
<>
{renderArtifacts(
item,
index,
true
)}
</>
) : null
})}
</div>
)}
{agent.messages.length > 0 && (
<MemoizedReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
@@ -1025,8 +1128,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
return !inline ? (
<CodeBlock
key={Math.random()}
chatflowid={chatflowid}
isDialog={isDialog}
chatflowid={
dialogProps.chatflow.id
}
isDialog={true}
language={
(match && match[1]) ||
''
@@ -1122,6 +1227,40 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
})}
</div>
)}
{message.usedTools && (
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
{message.usedTools.map((tool, index) => {
return (
<Chip
size='small'
key={index}
label={tool.tool}
component='a'
sx={{ mr: 1, mt: 1 }}
variant='outlined'
clickable
onClick={() => onSourceDialogClick(tool, 'Used Tools')}
/>
)
})}
</div>
)}
{message.artifacts && (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'column',
width: '100%'
}}
>
{message.artifacts.map((item, index) => {
return item !== null ? (
<>{renderArtifacts(item, index)}</>
) : null
})}
</div>
)}
<div className='markdownanswer'>
{/* Messages are being rendered in Markdown format */}
<MemoizedReactMarkdown