@carto/ps-react-ui 4.4.0-chat-ui.1 → 4.4.0-chat-ui.3

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.
Files changed (42) hide show
  1. package/dist/chat.js +1123 -393
  2. package/dist/chat.js.map +1 -1
  3. package/dist/components.js +141 -159
  4. package/dist/components.js.map +1 -1
  5. package/dist/copy-button-DGL1tyli.js +26 -0
  6. package/dist/copy-button-DGL1tyli.js.map +1 -0
  7. package/dist/index-BnyeR7Qx.js +6601 -0
  8. package/dist/index-BnyeR7Qx.js.map +1 -0
  9. package/dist/types/chat/const.d.ts +1 -0
  10. package/dist/types/chat/containers/styles.d.ts +6 -0
  11. package/dist/types/chat/feedback/chat-tool-code-area.d.ts +4 -0
  12. package/dist/types/chat/feedback/chat-tool-full-view-dialog.d.ts +2 -0
  13. package/dist/types/chat/feedback/chat-tool-group.d.ts +2 -0
  14. package/dist/types/chat/feedback/chat-tool-trace.d.ts +3 -0
  15. package/dist/types/chat/feedback/get-tool-label.d.ts +2 -0
  16. package/dist/types/chat/feedback/index.d.ts +4 -1
  17. package/dist/types/chat/feedback/styles.d.ts +149 -3
  18. package/dist/types/chat/index.d.ts +6 -3
  19. package/dist/types/chat/types.d.ts +58 -5
  20. package/dist/widgets/toolbar-actions.js +101 -6693
  21. package/dist/widgets/toolbar-actions.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/chat/bubbles/styles.ts +42 -0
  24. package/src/chat/const.ts +1 -0
  25. package/src/chat/containers/styles.ts +6 -0
  26. package/src/chat/feedback/chat-tool-code-area.test.tsx +23 -0
  27. package/src/chat/feedback/chat-tool-code-area.tsx +71 -0
  28. package/src/chat/feedback/chat-tool-full-view-dialog.test.tsx +39 -0
  29. package/src/chat/feedback/chat-tool-full-view-dialog.tsx +121 -0
  30. package/src/chat/feedback/chat-tool-group.test.tsx +84 -0
  31. package/src/chat/feedback/chat-tool-group.tsx +156 -0
  32. package/src/chat/feedback/chat-tool-trace.test.tsx +81 -0
  33. package/src/chat/feedback/chat-tool-trace.tsx +187 -0
  34. package/src/chat/feedback/get-tool-label.test.tsx +91 -0
  35. package/src/chat/feedback/get-tool-label.ts +13 -0
  36. package/src/chat/feedback/index.ts +4 -1
  37. package/src/chat/feedback/styles.ts +153 -4
  38. package/src/chat/index.ts +14 -3
  39. package/src/chat/types.ts +64 -5
  40. package/dist/types/chat/feedback/chat-tools.d.ts +0 -2
  41. package/src/chat/feedback/chat-tools.test.tsx +0 -23
  42. 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 { ChatTools } from './chat-tools'
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
- tools: {
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
- toolHeader: {
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
- gap: ({ spacing }) => spacing(1),
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
- toolStatusIcon: {
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
- ChatToolsProps,
19
+ ChatToolTraceProps,
20
+ ChatToolCodeAreaProps,
21
+ ChatToolFullViewDialogProps,
22
+ ChatToolGroupProps,
20
23
  } from './types'
21
24
 
22
25
  // Constants
23
- export { CHAT_MAX_WIDTH, CHAT_SCROLL_DELAY, CHAT_DIVIDER_DELAY } from './const'
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 { ChatTools } from './feedback/chat-tools'
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: 'loading' | 'thinking' | 'complete' | 'error'
116
- content?: ReactNode
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 ChatToolsProps extends ChatSxProps {
120
- tools: ChatToolItem[]
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
- title?: string
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,2 +0,0 @@
1
- import { ChatToolsProps } from '../types';
2
- export declare function ChatTools({ tools, labels, sx }: ChatToolsProps): import("react/jsx-runtime").JSX.Element;
@@ -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
- }