@carto/ps-react-ui 4.4.0-chat-ui.0 → 4.4.0-chat-ui.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat.js +1073 -390
- package/dist/chat.js.map +1 -1
- package/dist/components.js +141 -159
- package/dist/components.js.map +1 -1
- package/dist/copy-button-DGL1tyli.js +26 -0
- package/dist/copy-button-DGL1tyli.js.map +1 -0
- package/dist/index-BnyeR7Qx.js +6601 -0
- package/dist/index-BnyeR7Qx.js.map +1 -0
- package/dist/types/chat/const.d.ts +1 -0
- package/dist/types/chat/containers/styles.d.ts +3 -0
- package/dist/types/chat/feedback/chat-tool-code-area.d.ts +4 -0
- package/dist/types/chat/feedback/chat-tool-full-view-dialog.d.ts +2 -0
- package/dist/types/chat/feedback/chat-tool-group.d.ts +2 -0
- package/dist/types/chat/feedback/chat-tool-trace.d.ts +3 -0
- package/dist/types/chat/feedback/get-tool-label.d.ts +2 -0
- package/dist/types/chat/feedback/index.d.ts +4 -1
- package/dist/types/chat/feedback/styles.d.ts +149 -3
- package/dist/types/chat/index.d.ts +6 -3
- package/dist/types/chat/types.d.ts +58 -5
- package/dist/widgets/toolbar-actions.js +101 -6693
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/package.json +3 -3
- package/src/chat/const.ts +1 -0
- package/src/chat/containers/styles.ts +3 -0
- package/src/chat/feedback/chat-tool-code-area.test.tsx +23 -0
- package/src/chat/feedback/chat-tool-code-area.tsx +71 -0
- package/src/chat/feedback/chat-tool-full-view-dialog.test.tsx +39 -0
- package/src/chat/feedback/chat-tool-full-view-dialog.tsx +121 -0
- package/src/chat/feedback/chat-tool-group.test.tsx +84 -0
- package/src/chat/feedback/chat-tool-group.tsx +156 -0
- package/src/chat/feedback/chat-tool-trace.test.tsx +81 -0
- package/src/chat/feedback/chat-tool-trace.tsx +187 -0
- package/src/chat/feedback/get-tool-label.test.tsx +91 -0
- package/src/chat/feedback/get-tool-label.ts +13 -0
- package/src/chat/feedback/index.ts +4 -1
- package/src/chat/feedback/styles.ts +153 -4
- package/src/chat/index.ts +14 -3
- package/src/chat/types.ts +64 -5
- package/dist/types/chat/feedback/chat-tools.d.ts +0 -2
- package/src/chat/feedback/chat-tools.test.tsx +0 -23
- package/src/chat/feedback/chat-tools.tsx +0 -54
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
CircularProgress,
|
|
4
|
+
Typography,
|
|
5
|
+
Collapse,
|
|
6
|
+
Button,
|
|
7
|
+
} from '@mui/material'
|
|
8
|
+
import { ArrowRight, CheckCircle, Error } from '@mui/icons-material'
|
|
9
|
+
import type { ChatToolTraceProps } from '../types'
|
|
10
|
+
import { ChatToolCodeArea } from './chat-tool-code-area'
|
|
11
|
+
import { styles } from './styles'
|
|
12
|
+
import { ChatThinking } from './chat-thinking'
|
|
13
|
+
import { getToolLabel } from './get-tool-label'
|
|
14
|
+
import { McpTool } from '@carto/meridian-ds/custom-icons'
|
|
15
|
+
|
|
16
|
+
function TraceStatusIndicator({
|
|
17
|
+
status,
|
|
18
|
+
labels,
|
|
19
|
+
}: {
|
|
20
|
+
status: string
|
|
21
|
+
labels: ChatToolTraceProps['labels']
|
|
22
|
+
}) {
|
|
23
|
+
switch (status) {
|
|
24
|
+
case 'complete':
|
|
25
|
+
return (
|
|
26
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
27
|
+
<CheckCircle sx={{ fontSize: 12 }} color='success' />
|
|
28
|
+
<Typography variant='caption'>
|
|
29
|
+
{labels?.success ?? 'Success'}
|
|
30
|
+
</Typography>
|
|
31
|
+
</Box>
|
|
32
|
+
)
|
|
33
|
+
case 'error':
|
|
34
|
+
return (
|
|
35
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
36
|
+
<Error sx={{ fontSize: 12 }} color='error' />
|
|
37
|
+
<Typography variant='code3'>{labels?.error ?? 'Error'}</Typography>
|
|
38
|
+
</Box>
|
|
39
|
+
)
|
|
40
|
+
case 'running':
|
|
41
|
+
return <CircularProgress size={14} />
|
|
42
|
+
default:
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ChatToolTraceDetails({
|
|
48
|
+
tool,
|
|
49
|
+
labels = {},
|
|
50
|
+
}: Pick<ChatToolTraceProps, 'tool' | 'labels'>) {
|
|
51
|
+
const isError = tool.status === 'error'
|
|
52
|
+
const toolExecutedLabel = labels.toolExecuted ?? 'Tool executed'
|
|
53
|
+
const referenceLabel = labels.reference ?? 'Reference:'
|
|
54
|
+
const durationLabel = labels.duration ?? 'Duration:'
|
|
55
|
+
const statusLabel = labels.status ?? 'Status:'
|
|
56
|
+
const inputArgumentsLabel = labels.inputArguments ?? 'Input arguments:'
|
|
57
|
+
const outputLabel = labels.output ?? 'Output:'
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Box>
|
|
61
|
+
{tool.reference && (
|
|
62
|
+
<Box sx={styles.traceField}>
|
|
63
|
+
<Typography
|
|
64
|
+
variant='code3'
|
|
65
|
+
fontWeight={600}
|
|
66
|
+
color='text.secondary'
|
|
67
|
+
sx={styles.traceFieldLabel}
|
|
68
|
+
>
|
|
69
|
+
{referenceLabel}
|
|
70
|
+
</Typography>
|
|
71
|
+
<Box sx={styles.traceReference}>
|
|
72
|
+
<McpTool color='success' />
|
|
73
|
+
<Typography variant='code3' fontWeight={600}>
|
|
74
|
+
{tool.reference}
|
|
75
|
+
</Typography>
|
|
76
|
+
</Box>
|
|
77
|
+
</Box>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{tool.duration != null && (
|
|
81
|
+
<Box sx={styles.traceField}>
|
|
82
|
+
<Typography
|
|
83
|
+
variant='code3'
|
|
84
|
+
fontWeight={600}
|
|
85
|
+
color='text.secondary'
|
|
86
|
+
sx={styles.traceFieldLabel}
|
|
87
|
+
>
|
|
88
|
+
{durationLabel}
|
|
89
|
+
</Typography>
|
|
90
|
+
<Typography variant='code3'>{tool.duration}s</Typography>
|
|
91
|
+
</Box>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
<Box sx={styles.traceField}>
|
|
95
|
+
<Typography
|
|
96
|
+
variant='code3'
|
|
97
|
+
fontWeight={600}
|
|
98
|
+
color='text.secondary'
|
|
99
|
+
sx={styles.traceFieldLabel}
|
|
100
|
+
>
|
|
101
|
+
{statusLabel}
|
|
102
|
+
</Typography>
|
|
103
|
+
<TraceStatusIndicator status={tool.status} labels={labels} />
|
|
104
|
+
</Box>
|
|
105
|
+
|
|
106
|
+
{tool.inputArguments && (
|
|
107
|
+
<Box>
|
|
108
|
+
<Typography
|
|
109
|
+
variant='code3'
|
|
110
|
+
fontWeight={600}
|
|
111
|
+
color='text.secondary'
|
|
112
|
+
sx={styles.traceField}
|
|
113
|
+
>
|
|
114
|
+
{inputArgumentsLabel}
|
|
115
|
+
</Typography>
|
|
116
|
+
<ChatToolCodeArea
|
|
117
|
+
sx={{ mt: 0.5, mb: tool.output ? 1.5 : 0 }}
|
|
118
|
+
content={tool.inputArguments}
|
|
119
|
+
title={`${toolExecutedLabel}: ${inputArgumentsLabel}`}
|
|
120
|
+
isError={isError}
|
|
121
|
+
/>
|
|
122
|
+
</Box>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{tool.output && (
|
|
126
|
+
<Box>
|
|
127
|
+
<Typography
|
|
128
|
+
variant='code3'
|
|
129
|
+
fontWeight={600}
|
|
130
|
+
color='text.secondary'
|
|
131
|
+
sx={styles.traceField}
|
|
132
|
+
>
|
|
133
|
+
{outputLabel}
|
|
134
|
+
</Typography>
|
|
135
|
+
<ChatToolCodeArea
|
|
136
|
+
sx={{ mt: 0.5 }}
|
|
137
|
+
content={tool.output}
|
|
138
|
+
title={`${toolExecutedLabel}: ${outputLabel}`}
|
|
139
|
+
isError={isError}
|
|
140
|
+
/>
|
|
141
|
+
</Box>
|
|
142
|
+
)}
|
|
143
|
+
</Box>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function ChatToolTrace({
|
|
148
|
+
tool,
|
|
149
|
+
expanded,
|
|
150
|
+
onExpandedChange,
|
|
151
|
+
labels = {},
|
|
152
|
+
sx,
|
|
153
|
+
}: ChatToolTraceProps) {
|
|
154
|
+
const isRunning = tool.status === 'running'
|
|
155
|
+
const toolExecutedLabel = labels.toolExecuted ?? 'Tool executed'
|
|
156
|
+
|
|
157
|
+
if (isRunning) {
|
|
158
|
+
return <ChatThinking>{getToolLabel(tool)}</ChatThinking>
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<Box width='100%' sx={sx} className={`PsChat--tool-trace ${tool.name}`}>
|
|
163
|
+
<Button
|
|
164
|
+
size='small'
|
|
165
|
+
variant='text'
|
|
166
|
+
onClick={() => onExpandedChange?.(!expanded)}
|
|
167
|
+
aria-expanded={expanded}
|
|
168
|
+
sx={styles.traceHeader}
|
|
169
|
+
>
|
|
170
|
+
<Typography variant='caption' fontWeight={600} color='text.secondary'>
|
|
171
|
+
{toolExecutedLabel}
|
|
172
|
+
</Typography>
|
|
173
|
+
<ArrowRight
|
|
174
|
+
sx={{
|
|
175
|
+
...styles.traceChevron,
|
|
176
|
+
transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
179
|
+
</Button>
|
|
180
|
+
<Collapse in={expanded} unmountOnExit>
|
|
181
|
+
<Box sx={styles.traceDetailsWrapper}>
|
|
182
|
+
<ChatToolTraceDetails tool={tool} labels={labels} />
|
|
183
|
+
</Box>
|
|
184
|
+
</Collapse>
|
|
185
|
+
</Box>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest'
|
|
2
|
+
import { getToolLabel } from './get-tool-label'
|
|
3
|
+
import type { ChatToolItem } from '../types'
|
|
4
|
+
|
|
5
|
+
describe('getToolLabel', () => {
|
|
6
|
+
test('uses runningLabel when status is running', () => {
|
|
7
|
+
const tool: ChatToolItem = {
|
|
8
|
+
id: '1',
|
|
9
|
+
name: 'execute_sql',
|
|
10
|
+
status: 'running',
|
|
11
|
+
runningLabel: 'Running query',
|
|
12
|
+
}
|
|
13
|
+
expect(getToolLabel(tool)).toBe('Running query')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('falls back to capitalized name when running without runningLabel', () => {
|
|
17
|
+
const tool: ChatToolItem = {
|
|
18
|
+
id: '1',
|
|
19
|
+
name: 'execute_sql',
|
|
20
|
+
status: 'running',
|
|
21
|
+
}
|
|
22
|
+
expect(getToolLabel(tool)).toBe('Execute sql')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('uses label for completed status', () => {
|
|
26
|
+
const tool: ChatToolItem = {
|
|
27
|
+
id: '1',
|
|
28
|
+
name: 'execute_sql',
|
|
29
|
+
status: 'complete',
|
|
30
|
+
label: 'Database query',
|
|
31
|
+
}
|
|
32
|
+
expect(getToolLabel(tool)).toBe('Database query')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('uses label for error status', () => {
|
|
36
|
+
const tool: ChatToolItem = {
|
|
37
|
+
id: '1',
|
|
38
|
+
name: 'execute_sql',
|
|
39
|
+
status: 'error',
|
|
40
|
+
label: 'Database query',
|
|
41
|
+
}
|
|
42
|
+
expect(getToolLabel(tool)).toBe('Database query')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('falls back to capitalized name for non-running without label', () => {
|
|
46
|
+
const tool: ChatToolItem = {
|
|
47
|
+
id: '1',
|
|
48
|
+
name: 'add_marker',
|
|
49
|
+
status: 'complete',
|
|
50
|
+
}
|
|
51
|
+
expect(getToolLabel(tool)).toBe('Add marker')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('replaces underscores with spaces and capitalizes first letter', () => {
|
|
55
|
+
const tool: ChatToolItem = {
|
|
56
|
+
id: '1',
|
|
57
|
+
name: 'geocode_address_v2',
|
|
58
|
+
status: 'complete',
|
|
59
|
+
}
|
|
60
|
+
expect(getToolLabel(tool)).toBe('Geocode address v2')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('preserves names that are already formatted', () => {
|
|
64
|
+
const tool: ChatToolItem = {
|
|
65
|
+
id: '1',
|
|
66
|
+
name: 'Tool #1',
|
|
67
|
+
status: 'complete',
|
|
68
|
+
}
|
|
69
|
+
expect(getToolLabel(tool)).toBe('Tool #1')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('runningLabel does not leak into non-running statuses', () => {
|
|
73
|
+
const tool: ChatToolItem = {
|
|
74
|
+
id: '1',
|
|
75
|
+
name: 'execute_sql',
|
|
76
|
+
status: 'complete',
|
|
77
|
+
runningLabel: 'Running query',
|
|
78
|
+
}
|
|
79
|
+
expect(getToolLabel(tool)).toBe('Execute sql')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('label does not leak into running status', () => {
|
|
83
|
+
const tool: ChatToolItem = {
|
|
84
|
+
id: '1',
|
|
85
|
+
name: 'execute_sql',
|
|
86
|
+
status: 'running',
|
|
87
|
+
label: 'Database query',
|
|
88
|
+
}
|
|
89
|
+
expect(getToolLabel(tool)).toBe('Execute sql')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChatToolItem } from '../types'
|
|
2
|
+
|
|
3
|
+
function capitalize(str: string): string {
|
|
4
|
+
const spaced = str.replace(/_/g, ' ')
|
|
5
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getToolLabel(tool: ChatToolItem): string {
|
|
9
|
+
if (tool.status === 'running') {
|
|
10
|
+
return tool.runningLabel ?? capitalize(tool.name)
|
|
11
|
+
}
|
|
12
|
+
return tool.label ?? capitalize(tool.name)
|
|
13
|
+
}
|
|
@@ -2,4 +2,7 @@ export { ChatThinking } from './chat-thinking'
|
|
|
2
2
|
export { ChatLoader } from './chat-loader'
|
|
3
3
|
export { ChatActionsContainer } from './chat-actions-container'
|
|
4
4
|
export { ChatRatingAction } from './chat-rating-action'
|
|
5
|
-
export {
|
|
5
|
+
export { ChatToolTrace } from './chat-tool-trace'
|
|
6
|
+
export { ChatToolCodeArea } from './chat-tool-code-area'
|
|
7
|
+
export { ChatToolFullViewDialog } from './chat-tool-full-view-dialog'
|
|
8
|
+
export { ChatToolGroup } from './chat-tool-group'
|
|
@@ -65,16 +65,165 @@ export const styles = {
|
|
|
65
65
|
backgroundColor: ({ palette }) => palette.text.primary,
|
|
66
66
|
animation: `${breatheInner} 1s ease-in-out infinite`,
|
|
67
67
|
},
|
|
68
|
-
|
|
68
|
+
// --- Tool Trace styles ---
|
|
69
|
+
traceHeader: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
padding: 0,
|
|
73
|
+
paddingLeft: ({ spacing }) => spacing(0.5),
|
|
74
|
+
borderRadius: ({ spacing }) => spacing(0.5),
|
|
75
|
+
width: 'fit-content',
|
|
76
|
+
color: ({ palette }) => palette.text.secondary,
|
|
77
|
+
'&:hover': {
|
|
78
|
+
backgroundColor: ({ palette }) => palette.action.hover,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
traceChevron: {
|
|
82
|
+
color: ({ palette }) => palette.text.secondary,
|
|
83
|
+
transition: 'transform 0.2s',
|
|
84
|
+
},
|
|
85
|
+
traceDetailsWrapper: {
|
|
86
|
+
marginTop: ({ spacing }) => spacing(0.5),
|
|
87
|
+
padding: ({ spacing }) => spacing(1.5),
|
|
88
|
+
border: '1px solid',
|
|
89
|
+
borderColor: 'divider',
|
|
90
|
+
borderRadius: ({ spacing }) => spacing(1),
|
|
91
|
+
},
|
|
92
|
+
traceField: {
|
|
93
|
+
display: 'flex',
|
|
94
|
+
alignItems: 'flex-start',
|
|
95
|
+
gap: ({ spacing }) => spacing(1),
|
|
96
|
+
},
|
|
97
|
+
traceFieldLabel: {
|
|
98
|
+
flexShrink: 0,
|
|
99
|
+
},
|
|
100
|
+
traceReference: {
|
|
101
|
+
display: 'flex',
|
|
102
|
+
borderRadius: ({ spacing }) => spacing(0.25),
|
|
103
|
+
padding: ({ spacing }) => spacing(0, 0.5),
|
|
104
|
+
gap: ({ spacing }) => spacing(0.5),
|
|
105
|
+
backgroundColor: ({ palette }) => palette.success.relatedLight,
|
|
106
|
+
color: ({ palette }) => palette.success.dark,
|
|
107
|
+
},
|
|
108
|
+
traceStatusSuccess: {
|
|
109
|
+
color: ({ palette }) => palette.success.main,
|
|
110
|
+
},
|
|
111
|
+
traceStatusError: {
|
|
112
|
+
color: ({ palette }) => palette.error.main,
|
|
113
|
+
},
|
|
114
|
+
// --- Code Area styles ---
|
|
115
|
+
codeArea: {
|
|
116
|
+
position: 'relative',
|
|
69
117
|
width: '100%',
|
|
70
118
|
},
|
|
71
|
-
|
|
119
|
+
codeAreaPre: {
|
|
120
|
+
margin: 0,
|
|
121
|
+
padding: ({ spacing }) => spacing(1),
|
|
122
|
+
borderRadius: ({ spacing }) => spacing(0.5),
|
|
123
|
+
backgroundColor: ({ palette }) => palette.background.default,
|
|
124
|
+
fontSize: '0.75rem',
|
|
125
|
+
fontFamily: 'monospace',
|
|
126
|
+
whiteSpace: 'pre-wrap',
|
|
127
|
+
wordBreak: 'break-word',
|
|
128
|
+
overflowY: 'auto',
|
|
129
|
+
},
|
|
130
|
+
codeAreaPreError: {
|
|
131
|
+
borderLeft: ({ palette }) => `3px solid ${palette.error.main}`,
|
|
132
|
+
backgroundColor: ({ palette }) => `${palette.error.main}08`,
|
|
133
|
+
},
|
|
134
|
+
codeAreaFullViewButton: {
|
|
135
|
+
position: 'absolute',
|
|
136
|
+
top: ({ spacing }) => spacing(0.5),
|
|
137
|
+
right: ({ spacing }) => spacing(0.5),
|
|
138
|
+
},
|
|
139
|
+
// --- Full View Dialog styles ---
|
|
140
|
+
fullViewDialog: {
|
|
141
|
+
margin: ({ spacing }) => spacing(5),
|
|
142
|
+
},
|
|
143
|
+
fullViewPaper: {
|
|
144
|
+
borderRadius: 1,
|
|
145
|
+
},
|
|
146
|
+
fullViewTitle: {
|
|
72
147
|
display: 'flex',
|
|
73
148
|
alignItems: 'center',
|
|
74
|
-
|
|
149
|
+
justifyContent: 'space-between',
|
|
150
|
+
padding: ({ spacing }) => spacing(2),
|
|
151
|
+
borderBottom: '1px solid',
|
|
152
|
+
borderBottomColor: 'divider',
|
|
153
|
+
},
|
|
154
|
+
fullViewDialogContent: {
|
|
155
|
+
padding: 0,
|
|
156
|
+
'&:first-of-type': { paddingTop: 0 },
|
|
75
157
|
},
|
|
76
|
-
|
|
158
|
+
fullViewPre: {
|
|
159
|
+
margin: 0,
|
|
160
|
+
padding: ({ spacing }) => spacing(1),
|
|
161
|
+
background: ({ palette, spacing }) =>
|
|
162
|
+
`linear-gradient(to right, ${palette.action.hover} calc(${spacing(1)} + 3em), ${palette.background.default} calc(${spacing(1)} + 3em))`,
|
|
163
|
+
fontFamily: 'monospace',
|
|
164
|
+
fontSize: '0.8125rem',
|
|
165
|
+
whiteSpace: 'pre-wrap',
|
|
166
|
+
wordBreak: 'break-word',
|
|
167
|
+
lineHeight: 1.6,
|
|
168
|
+
counterReset: 'line',
|
|
169
|
+
},
|
|
170
|
+
fullViewLine: {
|
|
171
|
+
display: 'block',
|
|
172
|
+
'&::before': {
|
|
173
|
+
counterIncrement: 'line',
|
|
174
|
+
content: 'counter(line)',
|
|
175
|
+
display: 'inline-block',
|
|
176
|
+
width: '2em',
|
|
177
|
+
marginRight: '2em',
|
|
178
|
+
textAlign: 'right',
|
|
179
|
+
color: ({ palette }) => palette.text.secondary,
|
|
180
|
+
userSelect: 'none',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
groupHeader: {
|
|
184
|
+
textAlign: 'left',
|
|
77
185
|
display: 'flex',
|
|
78
186
|
alignItems: 'center',
|
|
187
|
+
borderRadius: 0,
|
|
188
|
+
gap: ({ spacing }) => spacing(0.5),
|
|
189
|
+
},
|
|
190
|
+
errorBadge: {
|
|
191
|
+
color: ({ palette }) => palette.error.main,
|
|
192
|
+
fontWeight: 600,
|
|
193
|
+
display: 'flex',
|
|
194
|
+
alignItems: 'center',
|
|
195
|
+
gap: ({ spacing }) => spacing(0.25),
|
|
196
|
+
},
|
|
197
|
+
// --- Syntax highlighting token styles ---
|
|
198
|
+
syntaxToken_key: {
|
|
199
|
+
color: '#881280',
|
|
200
|
+
},
|
|
201
|
+
syntaxToken_string: {
|
|
202
|
+
color: '#c41a16',
|
|
203
|
+
},
|
|
204
|
+
syntaxToken_number: {
|
|
205
|
+
color: '#1c00cf',
|
|
206
|
+
},
|
|
207
|
+
syntaxToken_boolean: {
|
|
208
|
+
color: '#1c00cf',
|
|
209
|
+
},
|
|
210
|
+
syntaxToken_null: {
|
|
211
|
+
color: '#808080',
|
|
212
|
+
},
|
|
213
|
+
syntaxToken_punctuation: {
|
|
214
|
+
color: ({ palette }) => palette.text.primary,
|
|
215
|
+
},
|
|
216
|
+
groupListItem: {
|
|
217
|
+
borderBottom: '1px solid',
|
|
218
|
+
borderColor: 'divider',
|
|
219
|
+
'&:first-of-type .MuiButton-root': {
|
|
220
|
+
borderRadius: ({ spacing }) => spacing(1, 1, 0, 0),
|
|
221
|
+
},
|
|
222
|
+
'&:last-of-type:not([aria-expanded=true]) .MuiButton-root': {
|
|
223
|
+
borderRadius: ({ spacing }) => spacing(0, 0, 1, 1),
|
|
224
|
+
},
|
|
225
|
+
'&:last-of-type': {
|
|
226
|
+
borderBottomWidth: 0,
|
|
227
|
+
},
|
|
79
228
|
},
|
|
80
229
|
} satisfies Record<string, SxProps<Theme>>
|
package/src/chat/index.ts
CHANGED
|
@@ -16,11 +16,19 @@ export type {
|
|
|
16
16
|
ChatActionsContainerProps,
|
|
17
17
|
ChatRatingActionProps,
|
|
18
18
|
ChatToolItem,
|
|
19
|
-
|
|
19
|
+
ChatToolTraceProps,
|
|
20
|
+
ChatToolCodeAreaProps,
|
|
21
|
+
ChatToolFullViewDialogProps,
|
|
22
|
+
ChatToolGroupProps,
|
|
20
23
|
} from './types'
|
|
21
24
|
|
|
22
25
|
// Constants
|
|
23
|
-
export {
|
|
26
|
+
export {
|
|
27
|
+
CHAT_MAX_WIDTH,
|
|
28
|
+
CHAT_SCROLL_DELAY,
|
|
29
|
+
CHAT_DIVIDER_DELAY,
|
|
30
|
+
CHAT_TOOL_CODE_AREA_MAX_HEIGHT,
|
|
31
|
+
} from './const'
|
|
24
32
|
|
|
25
33
|
// Messages
|
|
26
34
|
export { ChatUserMessage } from './bubbles/chat-user-message'
|
|
@@ -42,4 +50,7 @@ export { ChatStarter } from './containers/chat-starter'
|
|
|
42
50
|
// Feedback
|
|
43
51
|
export { ChatActionsContainer } from './feedback/chat-actions-container'
|
|
44
52
|
export { ChatRatingAction } from './feedback/chat-rating-action'
|
|
45
|
-
export {
|
|
53
|
+
export { ChatToolTrace } from './feedback/chat-tool-trace'
|
|
54
|
+
export { ChatToolCodeArea } from './feedback/chat-tool-code-area'
|
|
55
|
+
export { ChatToolFullViewDialog } from './feedback/chat-tool-full-view-dialog'
|
|
56
|
+
export { ChatToolGroup } from './feedback/chat-tool-group'
|
package/src/chat/types.ts
CHANGED
|
@@ -112,13 +112,72 @@ export interface ChatRatingActionProps {
|
|
|
112
112
|
export interface ChatToolItem {
|
|
113
113
|
id: string
|
|
114
114
|
name: string
|
|
115
|
-
status: '
|
|
116
|
-
|
|
115
|
+
status: 'running' | 'complete' | 'error'
|
|
116
|
+
/** Display label shown while status is 'running'. Falls back to a capitalized `name`. */
|
|
117
|
+
runningLabel?: string
|
|
118
|
+
/** Display label shown for non-running statuses. Falls back to a capitalized `name`. */
|
|
119
|
+
label?: string
|
|
120
|
+
/** Friendly reference name for the tool (e.g. "add_marker"). Displayed with icon. */
|
|
121
|
+
reference?: string
|
|
122
|
+
/** Execution duration in seconds (e.g. 1.8) */
|
|
123
|
+
duration?: number
|
|
124
|
+
/** Input arguments as a JSON string or plain text */
|
|
125
|
+
inputArguments?: string
|
|
126
|
+
/** Output as a JSON string or plain text */
|
|
127
|
+
output?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ChatToolTraceProps extends ChatSxProps {
|
|
131
|
+
tool: ChatToolItem
|
|
132
|
+
/** Whether the trace accordion is expanded */
|
|
133
|
+
expanded?: boolean
|
|
134
|
+
/** Callback when accordion expansion state changes */
|
|
135
|
+
onExpandedChange?: (expanded: boolean) => void
|
|
136
|
+
labels?: {
|
|
137
|
+
toolExecuted?: string
|
|
138
|
+
reference?: string
|
|
139
|
+
duration?: string
|
|
140
|
+
status?: string
|
|
141
|
+
inputArguments?: string
|
|
142
|
+
output?: string
|
|
143
|
+
fullView?: string
|
|
144
|
+
success?: string
|
|
145
|
+
error?: string
|
|
146
|
+
running?: string
|
|
147
|
+
}
|
|
117
148
|
}
|
|
118
149
|
|
|
119
|
-
export interface
|
|
120
|
-
|
|
150
|
+
export interface ChatToolCodeAreaProps extends ChatSxProps {
|
|
151
|
+
/** Code content to display */
|
|
152
|
+
content: string
|
|
153
|
+
/** Label for the full view dialog title */
|
|
154
|
+
title?: string
|
|
121
155
|
labels?: {
|
|
122
|
-
|
|
156
|
+
fullView?: string
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface ChatToolFullViewDialogProps {
|
|
161
|
+
open: boolean
|
|
162
|
+
onClose: () => void
|
|
163
|
+
title: string
|
|
164
|
+
content: string
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface ChatToolGroupProps extends ChatSxProps {
|
|
168
|
+
tools: ChatToolItem[]
|
|
169
|
+
/** Whether the group accordion is expanded */
|
|
170
|
+
expanded?: boolean
|
|
171
|
+
/** Callback when group expansion state changes */
|
|
172
|
+
onExpandedChange?: (expanded: boolean) => void
|
|
173
|
+
/** Map of tool IDs to their individual expanded state. Used to preserve expansion state during grouping. */
|
|
174
|
+
expandedTools?: Record<string, boolean>
|
|
175
|
+
/** Callback when an individual tool's expansion state changes */
|
|
176
|
+
onToolExpandedChange?: (
|
|
177
|
+
value: Record<string, boolean>,
|
|
178
|
+
toolId?: string,
|
|
179
|
+
) => void
|
|
180
|
+
labels?: ChatToolTraceProps['labels'] & {
|
|
181
|
+
toolsUsed?: string
|
|
123
182
|
}
|
|
124
183
|
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'vitest'
|
|
2
|
-
import { render, screen } from '@testing-library/react'
|
|
3
|
-
import { ChatTools } from './chat-tools'
|
|
4
|
-
|
|
5
|
-
describe('ChatTools', () => {
|
|
6
|
-
const defaultTools = [
|
|
7
|
-
{ id: '1', name: 'Search', status: 'complete' as const },
|
|
8
|
-
{ id: '2', name: 'Analyze', status: 'loading' as const },
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
test('renders tool names', () => {
|
|
12
|
-
render(<ChatTools tools={defaultTools} />)
|
|
13
|
-
expect(screen.getByText('Search')).toBeTruthy()
|
|
14
|
-
expect(screen.getByText('Analyze')).toBeTruthy()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('renders title label when provided', () => {
|
|
18
|
-
render(
|
|
19
|
-
<ChatTools tools={defaultTools} labels={{ title: 'Tool Activity' }} />,
|
|
20
|
-
)
|
|
21
|
-
expect(screen.getByText('Tool Activity')).toBeTruthy()
|
|
22
|
-
})
|
|
23
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Accordion,
|
|
3
|
-
AccordionDetails,
|
|
4
|
-
AccordionSummary,
|
|
5
|
-
Box,
|
|
6
|
-
CircularProgress,
|
|
7
|
-
Typography,
|
|
8
|
-
} from '@mui/material'
|
|
9
|
-
import {
|
|
10
|
-
CheckCircleOutline,
|
|
11
|
-
ErrorOutline,
|
|
12
|
-
ExpandMore,
|
|
13
|
-
} from '@mui/icons-material'
|
|
14
|
-
import type { ChatToolsProps, ChatToolItem } from '../types'
|
|
15
|
-
import { ChatThinking } from './chat-thinking'
|
|
16
|
-
import { styles } from './styles'
|
|
17
|
-
|
|
18
|
-
function ToolStatusIcon({ status }: { status: ChatToolItem['status'] }) {
|
|
19
|
-
switch (status) {
|
|
20
|
-
case 'loading':
|
|
21
|
-
return <CircularProgress size={16} />
|
|
22
|
-
case 'thinking':
|
|
23
|
-
return <ChatThinking>...</ChatThinking>
|
|
24
|
-
case 'complete':
|
|
25
|
-
return <CheckCircleOutline fontSize='small' color='success' />
|
|
26
|
-
case 'error':
|
|
27
|
-
return <ErrorOutline fontSize='small' color='error' />
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function ChatTools({ tools, labels = {}, sx }: ChatToolsProps) {
|
|
32
|
-
return (
|
|
33
|
-
<Box sx={{ ...styles.tools, ...sx }}>
|
|
34
|
-
{labels.title && (
|
|
35
|
-
<Typography variant='caption' color='text.secondary' sx={{ mb: 1 }}>
|
|
36
|
-
{labels.title}
|
|
37
|
-
</Typography>
|
|
38
|
-
)}
|
|
39
|
-
{tools.map((tool) => (
|
|
40
|
-
<Accordion key={tool.id} disableGutters elevation={0}>
|
|
41
|
-
<AccordionSummary expandIcon={<ExpandMore />}>
|
|
42
|
-
<Box sx={styles.toolHeader}>
|
|
43
|
-
<Box sx={styles.toolStatusIcon}>
|
|
44
|
-
<ToolStatusIcon status={tool.status} />
|
|
45
|
-
</Box>
|
|
46
|
-
<Typography variant='body2'>{tool.name}</Typography>
|
|
47
|
-
</Box>
|
|
48
|
-
</AccordionSummary>
|
|
49
|
-
{tool.content && <AccordionDetails>{tool.content}</AccordionDetails>}
|
|
50
|
-
</Accordion>
|
|
51
|
-
))}
|
|
52
|
-
</Box>
|
|
53
|
-
)
|
|
54
|
-
}
|