-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Feat: Add reasoning summary and tool details display for AI responses #14414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
972251d
758aa0c
8d3d278
6511a06
26830c4
6f7bd10
05d9e27
9ea64fb
3967f31
d998588
005e18a
990b8fc
4e628c2
7a9267b
09a1e13
c0ac85e
98225b9
c7e1fdc
82ab33e
579efc1
8150d10
a421ef2
1d4fd0d
49ddcba
14c42cf
46369d8
10ba534
57f3bbd
96e0ef8
e683c68
64bc0bb
6975d6f
871b1c9
64f79d7
1f2a8e4
84a94b3
5ff92db
169bd2f
87b5484
3f4100f
bc11610
b680c9d
57a9278
5e787c2
7d876ea
3723dc5
06c5c5f
0cec3d8
34c844f
a38c5b2
cbd0ca5
e5a7855
ce41d90
f16c223
002c021
e8458d9
d77b657
65d1146
e14d851
72486b1
4271c9c
cee588b
c831139
4388604
0cbdc9e
e1979de
fa1697c
6075c28
22d7f70
78ecbb2
72fa2ec
06a3eed
574f1c6
e618d7c
512b2f3
a7d59a9
de2de07
669bc46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { ErrorStepRenderer } from '@/ai/components/ErrorStepRenderer'; | ||
import { ReasoningSummaryDisplay } from '@/ai/components/ReasoningSummaryDisplay'; | ||
import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer'; | ||
import type { ParsedStep } from '@/ai/types/streamTypes'; | ||
import { parseStream } from '@/ai/utils/parseStream'; | ||
import { IconDotsVertical } from 'twenty-ui/display'; | ||
|
||
import { LazyMarkdownRenderer } from '@/ai/components/LazyMarkdownRenderer'; | ||
import { agentStreamingMessageState } from '@/ai/states/agentStreamingMessageState'; | ||
import { keyframes, useTheme } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
import { useRecoilValue } from 'recoil'; | ||
|
||
const StyledStepsContainer = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: ${({ theme }) => theme.spacing(1)}; | ||
`; | ||
|
||
const StyledDotsIconContainer = styled.div` | ||
align-items: center; | ||
border: ${({ theme }) => `1px solid ${theme.border.color.light}`}; | ||
border-radius: ${({ theme }) => theme.border.radius.md}; | ||
display: flex; | ||
justify-content: center; | ||
padding-inline: ${({ theme }) => theme.spacing(1)}; | ||
`; | ||
|
||
const StyledDotsIcon = styled(IconDotsVertical)` | ||
color: ${({ theme }) => theme.font.color.light}; | ||
transform: rotate(90deg); | ||
`; | ||
|
||
const dots = keyframes` | ||
0% { content: ''; } | ||
33% { content: '.'; } | ||
66% { content: '..'; } | ||
100% { content: '...'; } | ||
`; | ||
|
||
const StyledToolCallContainer = styled.div` | ||
&::after { | ||
display: inline-block; | ||
content: ''; | ||
animation: ${dots} 750ms steps(3, end) infinite; | ||
width: 2ch; | ||
text-align: left; | ||
} | ||
`; | ||
|
||
const LoadingDotsIcon = () => { | ||
const theme = useTheme(); | ||
|
||
return ( | ||
<StyledDotsIconContainer> | ||
<StyledDotsIcon size={theme.icon.size.xl} /> | ||
</StyledDotsIconContainer> | ||
); | ||
}; | ||
|
||
export const AIChatAssistantMessageRenderer = ({ | ||
streamData, | ||
}: { | ||
streamData: string; | ||
}) => { | ||
const agentStreamingMessage = useRecoilValue(agentStreamingMessageState); | ||
const isStreaming = | ||
Boolean(agentStreamingMessage) && streamData === agentStreamingMessage; | ||
|
||
if (!streamData) { | ||
return <LoadingDotsIcon />; | ||
} | ||
|
||
const isPlainString = | ||
!streamData.includes('\n') || | ||
!streamData.split('\n').some((line) => { | ||
try { | ||
JSON.parse(line); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
}); | ||
|
||
if (isPlainString) { | ||
abdulrahmancodes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return <LazyMarkdownRenderer text={streamData} />; | ||
} | ||
|
||
const steps = parseStream(streamData); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider memoizing parseStream result with useMemo to avoid reparsing stream data on every render, especially for large streams. |
||
|
||
if (!steps.length) { | ||
return <LoadingDotsIcon />; | ||
} | ||
|
||
const renderStep = (step: ParsedStep, index: number) => { | ||
switch (step.type) { | ||
case 'tool': | ||
return <ToolStepRenderer key={index} events={step.events} />; | ||
case 'reasoning': | ||
return ( | ||
<ReasoningSummaryDisplay | ||
key={index} | ||
content={step.content} | ||
isThinking={step.isThinking} | ||
/> | ||
); | ||
case 'text': | ||
return <LazyMarkdownRenderer key={index} text={step.content} />; | ||
case 'error': | ||
return ( | ||
<ErrorStepRenderer | ||
key={index} | ||
message={step.message} | ||
error={step.error} | ||
/> | ||
); | ||
default: | ||
return null; | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<StyledStepsContainer>{steps.map(renderStep)}</StyledStepsContainer> | ||
{isStreaming && <StyledToolCallContainer />} | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,61 @@ | ||||||||||||||||||||||||||||||||||
import { extractErrorMessage } from '@/ai/utils/extractErrorMessage'; | ||||||||||||||||||||||||||||||||||
import { useTheme } from '@emotion/react'; | ||||||||||||||||||||||||||||||||||
import styled from '@emotion/styled'; | ||||||||||||||||||||||||||||||||||
import { IconAlertCircle } from 'twenty-ui/display'; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const StyledContainer = styled.div` | ||||||||||||||||||||||||||||||||||
align-items: flex-start; | ||||||||||||||||||||||||||||||||||
background-color: ${({ theme }) => theme.color.red10}; | ||||||||||||||||||||||||||||||||||
border: 1px solid ${({ theme }) => theme.color.red20}; | ||||||||||||||||||||||||||||||||||
border-radius: ${({ theme }) => theme.border.radius.md}; | ||||||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||||||
gap: ${({ theme }) => theme.spacing(2)}; | ||||||||||||||||||||||||||||||||||
margin-block: ${({ theme }) => theme.spacing(2)}; | ||||||||||||||||||||||||||||||||||
padding: ${({ theme }) => theme.spacing(3)}; | ||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const StyledIconContainer = styled.div` | ||||||||||||||||||||||||||||||||||
align-items: center; | ||||||||||||||||||||||||||||||||||
color: ${({ theme }) => theme.color.red60}; | ||||||||||||||||||||||||||||||||||
display: flex; | ||||||||||||||||||||||||||||||||||
flex-shrink: 0; | ||||||||||||||||||||||||||||||||||
justify-content: center; | ||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const StyledContent = styled.div` | ||||||||||||||||||||||||||||||||||
flex: 1; | ||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const StyledTitle = styled.div` | ||||||||||||||||||||||||||||||||||
font-weight: ${({ theme }) => theme.font.weight.medium}; | ||||||||||||||||||||||||||||||||||
color: ${({ theme }) => theme.color.red80}; | ||||||||||||||||||||||||||||||||||
margin-bottom: ${({ theme }) => theme.spacing(1)}; | ||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const StyledMessage = styled.div` | ||||||||||||||||||||||||||||||||||
color: ${({ theme }) => theme.color.red70}; | ||||||||||||||||||||||||||||||||||
line-height: ${({ theme }) => theme.text.lineHeight.lg}; | ||||||||||||||||||||||||||||||||||
`; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
export const ErrorStepRenderer = ({ | ||||||||||||||||||||||||||||||||||
message, | ||||||||||||||||||||||||||||||||||
error, | ||||||||||||||||||||||||||||||||||
}: { | ||||||||||||||||||||||||||||||||||
message: string; | ||||||||||||||||||||||||||||||||||
error?: unknown; | ||||||||||||||||||||||||||||||||||
}) => { | ||||||||||||||||||||||||||||||||||
Comment on lines
+40
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider defining a TypeScript interface for component props to enhance maintainability, following the project's conventions seen in other components.
Suggested change
Context Used: Context - Use TypeScript interface definitions for component props to enhance maintainability. (link) |
||||||||||||||||||||||||||||||||||
const theme = useTheme(); | ||||||||||||||||||||||||||||||||||
const errorMessage = error ? extractErrorMessage(error) : message; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||
<StyledContainer> | ||||||||||||||||||||||||||||||||||
<StyledIconContainer> | ||||||||||||||||||||||||||||||||||
<IconAlertCircle size={theme.icon.size.md} /> | ||||||||||||||||||||||||||||||||||
</StyledIconContainer> | ||||||||||||||||||||||||||||||||||
<StyledContent> | ||||||||||||||||||||||||||||||||||
<StyledTitle>Error</StyledTitle> | ||||||||||||||||||||||||||||||||||
abdulrahmancodes marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
<StyledMessage>{errorMessage}</StyledMessage> | ||||||||||||||||||||||||||||||||||
</StyledContent> | ||||||||||||||||||||||||||||||||||
</StyledContainer> | ||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||
}; |
Uh oh!
There was an error while loading. Please reload this page.