@gram-ai/elements 1.25.0 → 1.25.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/components/Chat/stories/MessageFeedback.stories.d.ts +1 -1
- package/dist/components/Replay.stories.d.ts +16 -0
- package/dist/contexts/ChatIdContext.d.ts +11 -0
- package/dist/contexts/contexts.d.ts +1 -0
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +7 -6
- package/dist/{index-iUSSoKFz.cjs → index-B8nSCdu4.cjs} +11 -11
- package/dist/index-B8nSCdu4.cjs.map +1 -0
- package/dist/{index-wBHCO1r-.cjs → index-CAtaLV1E.cjs} +64 -55
- package/dist/index-CAtaLV1E.cjs.map +1 -0
- package/dist/{index-CtyV0c-T.js → index-CJrwma08.js} +3737 -3730
- package/dist/index-CJrwma08.js.map +1 -0
- package/dist/{index-DDb23655.js → index-DLWQ91ow.js} +8494 -8418
- package/dist/index-DLWQ91ow.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/lib/messageConverter.d.ts +1 -1
- package/dist/lib/messageConverter.test.d.ts +1 -0
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-CGIJBY8c.js → profiler-BaG0scxd.js} +2 -2
- package/dist/{profiler-CGIJBY8c.js.map → profiler-BaG0scxd.js.map} +1 -1
- package/dist/{profiler-CLtQEzfv.cjs → profiler-CuqENACf.cjs} +2 -2
- package/dist/{profiler-CLtQEzfv.cjs.map → profiler-CuqENACf.cjs.map} +1 -1
- package/dist/{startRecording-x0G7lOpP.js → startRecording-86bHmd-l.js} +2 -2
- package/dist/{startRecording-x0G7lOpP.js.map → startRecording-86bHmd-l.js.map} +1 -1
- package/dist/{startRecording-DXZPNn9e.cjs → startRecording-BiLmoqZa.cjs} +2 -2
- package/dist/{startRecording-DXZPNn9e.cjs.map → startRecording-BiLmoqZa.cjs.map} +1 -1
- package/dist/types/index.d.ts +4 -4
- package/package.json +1 -1
- package/src/components/Chat/stories/MessageFeedback.stories.tsx +6 -6
- package/src/components/Chat/stories/ToolApproval.stories.tsx +10 -10
- package/src/components/Chat/stories/Tools.stories.tsx +122 -104
- package/src/components/Chat/stories/Variants.stories.tsx +1 -1
- package/src/components/Replay.stories.tsx +230 -0
- package/src/components/ShadowRoot.tsx +5 -1
- package/src/components/assistant-ui/message-feedback.tsx +6 -7
- package/src/components/assistant-ui/thread.tsx +76 -11
- package/src/contexts/ChatIdContext.tsx +21 -0
- package/src/contexts/ElementsProvider.tsx +77 -37
- package/src/contexts/contexts.ts +2 -0
- package/src/hooks/useAuth.ts +1 -2
- package/src/index.ts +1 -0
- package/src/lib/messageConverter.test.ts +242 -0
- package/src/lib/messageConverter.ts +22 -10
- package/src/types/index.ts +4 -4
- package/dist/index-CtyV0c-T.js.map +0 -1
- package/dist/index-DDb23655.js.map +0 -1
- package/dist/index-iUSSoKFz.cjs.map +0 -1
- package/dist/index-wBHCO1r-.cjs.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ToolCallMessagePartProps } from '@assistant-ui/react'
|
|
2
2
|
import type { Meta, StoryFn } from '@storybook/react-vite'
|
|
3
|
-
import React from 'react'
|
|
3
|
+
import React, { useState, useCallback } from 'react'
|
|
4
4
|
import z from 'zod'
|
|
5
5
|
import { Chat } from '..'
|
|
6
|
+
import { useToolExecution } from '../../../contexts/ToolExecutionContext'
|
|
6
7
|
import { defineFrontendTool } from '../../../lib/tools'
|
|
7
8
|
|
|
8
9
|
const meta: Meta<typeof Chat> = {
|
|
@@ -17,128 +18,145 @@ export default meta
|
|
|
17
18
|
|
|
18
19
|
type Story = StoryFn<typeof Chat>
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const ProductCardComponent = ({ result }: ToolCallMessagePartProps) => {
|
|
22
|
+
const { executeTool, isToolAvailable } = useToolExecution()
|
|
23
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
24
|
+
const [addedToCart, setAddedToCart] = useState(false)
|
|
25
|
+
|
|
26
|
+
// Parse the result to get product details
|
|
27
|
+
let product = {
|
|
28
|
+
id: '',
|
|
29
|
+
name: 'Loading...',
|
|
30
|
+
description: '',
|
|
31
|
+
price: 0,
|
|
32
|
+
category: '',
|
|
33
|
+
rating: 0,
|
|
34
|
+
reviewCount: 0,
|
|
35
|
+
imageUrl: '',
|
|
36
|
+
inStock: true,
|
|
37
|
+
}
|
|
25
38
|
|
|
26
|
-
// Parse the result to get the pin
|
|
27
|
-
let pin = '****'
|
|
28
39
|
try {
|
|
29
40
|
if (result) {
|
|
30
41
|
const parsed = typeof result === 'string' ? JSON.parse(result) : result
|
|
31
42
|
if (parsed?.content?.[0]?.text) {
|
|
32
43
|
const content = JSON.parse(parsed.content[0].text)
|
|
33
|
-
|
|
34
|
-
} else if (parsed?.
|
|
35
|
-
|
|
44
|
+
product = { ...product, ...content }
|
|
45
|
+
} else if (parsed?.name) {
|
|
46
|
+
product = { ...product, ...parsed }
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
49
|
} catch {
|
|
39
50
|
// Fallback to default
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
const
|
|
43
|
-
const cardNumber = args?.queryParameters?.cardNumber || '4532 •••• •••• 1234'
|
|
44
|
-
const cardHolder = 'JOHN DOE'
|
|
45
|
-
const expiry = '12/25'
|
|
46
|
-
const cvv = '123'
|
|
53
|
+
const canAddToCart = isToolAvailable('ecommerce_api_add_to_cart')
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
return
|
|
50
|
-
}
|
|
55
|
+
const handleAddToCart = useCallback(async () => {
|
|
56
|
+
if (!product.id || !canAddToCart) return
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<div className="absolute inset-0 backface-hidden">
|
|
62
|
-
<div className="relative h-full w-full overflow-hidden rounded-xl bg-gradient-to-br from-indigo-600 via-purple-600 to-pink-500 p-6 text-white shadow-2xl">
|
|
63
|
-
{/* Card pattern overlay */}
|
|
64
|
-
<div className="absolute inset-0 opacity-10">
|
|
65
|
-
<div className="absolute -top-10 -right-10 h-40 w-40 rounded-full bg-white"></div>
|
|
66
|
-
<div className="absolute -bottom-10 -left-10 h-32 w-32 rounded-full bg-white"></div>
|
|
67
|
-
</div>
|
|
58
|
+
setIsLoading(true)
|
|
59
|
+
try {
|
|
60
|
+
// HTTP tools from OpenAPI expect body content wrapped in a 'body' field
|
|
61
|
+
const toolResult = await executeTool('ecommerce_api_add_to_cart', {
|
|
62
|
+
body: {
|
|
63
|
+
productId: product.id,
|
|
64
|
+
quantity: 1,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
if (toolResult.success) {
|
|
69
|
+
setAddedToCart(true)
|
|
70
|
+
} else {
|
|
71
|
+
console.error('[ProductCard] Tool failed:', toolResult.error)
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('[ProductCard] Exception:', err)
|
|
75
|
+
} finally {
|
|
76
|
+
setIsLoading(false)
|
|
77
|
+
}
|
|
78
|
+
}, [product.id, canAddToCart, executeTool])
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</div>
|
|
80
|
+
return (
|
|
81
|
+
<div className="my-4 w-80">
|
|
82
|
+
<div className="overflow-hidden rounded-xl bg-white shadow-lg dark:bg-slate-800">
|
|
83
|
+
{/* Product Image */}
|
|
84
|
+
<div className="relative h-48 bg-gradient-to-br from-indigo-100 to-purple-100 dark:from-indigo-900 dark:to-purple-900">
|
|
85
|
+
{product.imageUrl ? (
|
|
86
|
+
<img
|
|
87
|
+
src={product.imageUrl}
|
|
88
|
+
alt={product.name}
|
|
89
|
+
className="h-full w-full object-cover"
|
|
90
|
+
/>
|
|
91
|
+
) : (
|
|
92
|
+
<div className="flex h-full items-center justify-center">
|
|
93
|
+
<span className="text-6xl">📦</span>
|
|
91
94
|
</div>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<div className="absolute right-2
|
|
95
|
-
|
|
95
|
+
)}
|
|
96
|
+
{!product.inStock && (
|
|
97
|
+
<div className="absolute top-2 right-2 rounded-full bg-red-500 px-2 py-1 text-xs font-semibold text-white">
|
|
98
|
+
Out of Stock
|
|
96
99
|
</div>
|
|
97
|
-
|
|
100
|
+
)}
|
|
98
101
|
</div>
|
|
99
102
|
|
|
100
|
-
{/*
|
|
101
|
-
<div className="
|
|
102
|
-
<div className="
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{cvv}
|
|
112
|
-
</div>
|
|
113
|
-
<div className="text-xs opacity-70">CVV</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
{/* PIN Display */}
|
|
117
|
-
<div className="mt-6 space-y-2">
|
|
118
|
-
<div className="text-xs opacity-70">PIN</div>
|
|
119
|
-
<div className="flex items-center gap-3">
|
|
120
|
-
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-gradient-to-br from-yellow-400 to-orange-500 shadow-lg">
|
|
121
|
-
<span className="text-2xl font-bold text-white">
|
|
122
|
-
{pin}
|
|
123
|
-
</span>
|
|
124
|
-
</div>
|
|
125
|
-
<div className="text-xs opacity-60">
|
|
126
|
-
Keep this PIN secure
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
103
|
+
{/* Product Details */}
|
|
104
|
+
<div className="p-4">
|
|
105
|
+
<div className="mb-1 text-xs font-medium tracking-wide text-indigo-500 uppercase dark:text-indigo-400">
|
|
106
|
+
{product.category}
|
|
107
|
+
</div>
|
|
108
|
+
<h3 className="mb-2 text-lg font-bold text-slate-900 dark:text-white">
|
|
109
|
+
{product.name}
|
|
110
|
+
</h3>
|
|
111
|
+
<p className="mb-3 line-clamp-2 text-sm text-slate-600 dark:text-slate-300">
|
|
112
|
+
{product.description}
|
|
113
|
+
</p>
|
|
131
114
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
{/* Rating */}
|
|
116
|
+
<div className="mb-3 flex items-center gap-1">
|
|
117
|
+
<div className="flex">
|
|
118
|
+
{[1, 2, 3, 4, 5].map((star) => (
|
|
119
|
+
<span
|
|
120
|
+
key={star}
|
|
121
|
+
className={
|
|
122
|
+
star <= Math.round(product.rating)
|
|
123
|
+
? 'text-yellow-400'
|
|
124
|
+
: 'text-slate-300'
|
|
125
|
+
}
|
|
126
|
+
>
|
|
127
|
+
★
|
|
128
|
+
</span>
|
|
129
|
+
))}
|
|
136
130
|
</div>
|
|
131
|
+
<span className="text-sm text-slate-500">
|
|
132
|
+
({product.reviewCount} reviews)
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
{/* Price and Add to Cart */}
|
|
137
|
+
<div className="flex items-center justify-between">
|
|
138
|
+
<span className="text-2xl font-bold text-slate-900 dark:text-white">
|
|
139
|
+
${product.price?.toFixed(2)}
|
|
140
|
+
</span>
|
|
141
|
+
<button
|
|
142
|
+
onClick={handleAddToCart}
|
|
143
|
+
disabled={
|
|
144
|
+
isLoading || addedToCart || !canAddToCart || !product.inStock
|
|
145
|
+
}
|
|
146
|
+
className={`rounded-lg px-4 py-2 text-sm font-semibold text-white transition-colors ${
|
|
147
|
+
addedToCart
|
|
148
|
+
? 'bg-green-500'
|
|
149
|
+
: isLoading
|
|
150
|
+
? 'bg-indigo-400'
|
|
151
|
+
: 'bg-indigo-600 hover:bg-indigo-700'
|
|
152
|
+
} disabled:cursor-not-allowed disabled:opacity-50`}
|
|
153
|
+
>
|
|
154
|
+
{addedToCart
|
|
155
|
+
? '✓ Added'
|
|
156
|
+
: isLoading
|
|
157
|
+
? 'Adding...'
|
|
158
|
+
: 'Add to Cart'}
|
|
159
|
+
</button>
|
|
142
160
|
</div>
|
|
143
161
|
</div>
|
|
144
162
|
</div>
|
|
@@ -154,15 +172,15 @@ CustomToolComponent.parameters = {
|
|
|
154
172
|
welcome: {
|
|
155
173
|
suggestions: [
|
|
156
174
|
{
|
|
157
|
-
title: 'Get
|
|
158
|
-
label: '
|
|
159
|
-
prompt: '
|
|
175
|
+
title: 'Get product details',
|
|
176
|
+
label: 'View a product',
|
|
177
|
+
prompt: 'List products and then show me details for the first one',
|
|
160
178
|
},
|
|
161
179
|
],
|
|
162
180
|
},
|
|
163
181
|
tools: {
|
|
164
182
|
components: {
|
|
165
|
-
|
|
183
|
+
ecommerce_api_get_product: ProductCardComponent,
|
|
166
184
|
},
|
|
167
185
|
},
|
|
168
186
|
},
|
|
@@ -61,7 +61,7 @@ StandaloneWithHistory.parameters = {
|
|
|
61
61
|
history: { enabled: true, showThreadList: true },
|
|
62
62
|
model: { showModelPicker: true },
|
|
63
63
|
tools: {
|
|
64
|
-
toolsRequiringApproval: ['
|
|
64
|
+
toolsRequiringApproval: ['ecommerce_api_create_order'],
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
},
|
|
@@ -141,6 +141,178 @@ const reasoningCassette: Cassette = {
|
|
|
141
141
|
],
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Simulates what the converter produced BEFORE the dedup fix (AGE-1295).
|
|
146
|
+
*
|
|
147
|
+
* The server accumulates all tool calls into each assistant message, so when
|
|
148
|
+
* assistant-ui merges consecutive assistant chunks into a single displayed
|
|
149
|
+
* message, the parts look like:
|
|
150
|
+
* chunk 1: text + tc_1
|
|
151
|
+
* chunk 2: text + tc_1 + tc_2 ← tc_1 duplicated
|
|
152
|
+
* chunk 3: text + tc_1 + tc_2 + tc_3 ← tc_1, tc_2 duplicated
|
|
153
|
+
*
|
|
154
|
+
* This causes every tool group to show "Executed 3 tools" instead of 1 each.
|
|
155
|
+
*/
|
|
156
|
+
const beforeFixCassette: Cassette = {
|
|
157
|
+
messages: [
|
|
158
|
+
{
|
|
159
|
+
role: 'user',
|
|
160
|
+
content: [
|
|
161
|
+
{ type: 'text', text: 'Search for 3 deals in HubSpot for me.' },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
role: 'assistant',
|
|
166
|
+
content: [
|
|
167
|
+
// --- chunk 1: first attempt ---
|
|
168
|
+
{
|
|
169
|
+
type: 'text',
|
|
170
|
+
text: "I'll search for 3 deals in HubSpot for you.",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
type: 'tool-call',
|
|
174
|
+
toolCallId: 'tc_deals_1',
|
|
175
|
+
toolName: 'hubspot_search_deals',
|
|
176
|
+
args: { query: 'deals', limit: 3 },
|
|
177
|
+
result: { error: 'Invalid filter groups structure' },
|
|
178
|
+
},
|
|
179
|
+
// --- chunk 2: second attempt (accumulates tc_1 + tc_2) ---
|
|
180
|
+
{
|
|
181
|
+
type: 'text',
|
|
182
|
+
text: 'Let me try a different approach to retrieve deals:',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
type: 'tool-call',
|
|
186
|
+
toolCallId: 'tc_deals_1_dup',
|
|
187
|
+
toolName: 'hubspot_search_deals',
|
|
188
|
+
args: { query: 'deals', limit: 3 },
|
|
189
|
+
result: { error: 'Invalid filter groups structure' },
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
type: 'tool-call',
|
|
193
|
+
toolCallId: 'tc_deals_2',
|
|
194
|
+
toolName: 'hubspot_search_deals',
|
|
195
|
+
args: { query: 'deals', limit: 3, filterGroups: [] },
|
|
196
|
+
result: { error: 'Filter groups must not be empty' },
|
|
197
|
+
},
|
|
198
|
+
// --- chunk 3: third attempt (accumulates tc_1 + tc_2 + tc_3) ---
|
|
199
|
+
{
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: 'Let me try with proper filter groups structure:',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'tool-call',
|
|
205
|
+
toolCallId: 'tc_deals_1_dup2',
|
|
206
|
+
toolName: 'hubspot_search_deals',
|
|
207
|
+
args: { query: 'deals', limit: 3 },
|
|
208
|
+
result: { error: 'Invalid filter groups structure' },
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: 'tool-call',
|
|
212
|
+
toolCallId: 'tc_deals_2_dup',
|
|
213
|
+
toolName: 'hubspot_search_deals',
|
|
214
|
+
args: { query: 'deals', limit: 3, filterGroups: [] },
|
|
215
|
+
result: { error: 'Filter groups must not be empty' },
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: 'tool-call',
|
|
219
|
+
toolCallId: 'tc_deals_3',
|
|
220
|
+
toolName: 'hubspot_search_deals',
|
|
221
|
+
args: {
|
|
222
|
+
limit: 3,
|
|
223
|
+
filterGroups: [
|
|
224
|
+
{
|
|
225
|
+
filters: [
|
|
226
|
+
{ propertyName: 'dealname', operator: 'HAS_PROPERTY' },
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
result: {
|
|
232
|
+
deals: [
|
|
233
|
+
{ id: '1', name: 'Acme Corp', amount: 50000 },
|
|
234
|
+
{ id: '2', name: 'Globex Inc', amount: 75000 },
|
|
235
|
+
{ id: '3', name: 'Initech LLC', amount: 30000 },
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
type: 'text',
|
|
241
|
+
text: 'Here are the 3 deals I found:\n\n1. **Acme Corp** — $50,000\n2. **Globex Inc** — $75,000\n3. **Initech LLC** — $30,000',
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const interleavedToolCallsCassette: Cassette = {
|
|
249
|
+
messages: [
|
|
250
|
+
{
|
|
251
|
+
role: 'user',
|
|
252
|
+
content: [
|
|
253
|
+
{ type: 'text', text: 'Search for 3 deals in HubSpot for me.' },
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
role: 'assistant',
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: 'text',
|
|
261
|
+
text: "I'll search for 3 deals in HubSpot for you.",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
type: 'tool-call',
|
|
265
|
+
toolCallId: 'tc_deals_1',
|
|
266
|
+
toolName: 'hubspot_search_deals',
|
|
267
|
+
args: { query: 'deals', limit: 3 },
|
|
268
|
+
result: { error: 'Invalid filter groups structure' },
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: 'Let me try a different approach to retrieve deals:',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
type: 'tool-call',
|
|
276
|
+
toolCallId: 'tc_deals_2',
|
|
277
|
+
toolName: 'hubspot_search_deals',
|
|
278
|
+
args: { query: 'deals', limit: 3, filterGroups: [] },
|
|
279
|
+
result: { error: 'Filter groups must not be empty' },
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: 'text',
|
|
283
|
+
text: 'Let me try with proper filter groups structure:',
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'tool-call',
|
|
287
|
+
toolCallId: 'tc_deals_3',
|
|
288
|
+
toolName: 'hubspot_search_deals',
|
|
289
|
+
args: {
|
|
290
|
+
limit: 3,
|
|
291
|
+
filterGroups: [
|
|
292
|
+
{
|
|
293
|
+
filters: [
|
|
294
|
+
{ propertyName: 'dealname', operator: 'HAS_PROPERTY' },
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
result: {
|
|
300
|
+
deals: [
|
|
301
|
+
{ id: '1', name: 'Acme Corp', amount: 50000 },
|
|
302
|
+
{ id: '2', name: 'Globex Inc', amount: 75000 },
|
|
303
|
+
{ id: '3', name: 'Initech LLC', amount: 30000 },
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
type: 'text',
|
|
309
|
+
text: 'Here are the 3 deals I found:\n\n1. **Acme Corp** — $50,000\n2. **Globex Inc** — $75,000\n3. **Initech LLC** — $30,000',
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
}
|
|
315
|
+
|
|
144
316
|
const multiTurnCassette: Cassette = {
|
|
145
317
|
messages: [
|
|
146
318
|
{
|
|
@@ -240,6 +412,64 @@ ToolCalls.decorators = [
|
|
|
240
412
|
),
|
|
241
413
|
]
|
|
242
414
|
|
|
415
|
+
/**
|
|
416
|
+
* BEFORE the fix (AGE-1295): simulates the duplicated tool calls that the
|
|
417
|
+
* converter used to produce. Each tool group shows an inflated count because
|
|
418
|
+
* the server's accumulated tool_calls were not deduplicated.
|
|
419
|
+
*
|
|
420
|
+
* Expected: groups show "Executed 2 tools" and "Executed 3 tools" instead
|
|
421
|
+
* of 1 tool each — this is the bug.
|
|
422
|
+
*/
|
|
423
|
+
export const BeforeFix_DuplicatedToolCalls: Story = () => (
|
|
424
|
+
<Replay
|
|
425
|
+
cassette={beforeFixCassette}
|
|
426
|
+
config={{
|
|
427
|
+
variant: 'standalone',
|
|
428
|
+
tools: { expandToolGroupsByDefault: false },
|
|
429
|
+
}}
|
|
430
|
+
typingSpeed={0}
|
|
431
|
+
userMessageDelay={0}
|
|
432
|
+
assistantStartDelay={0}
|
|
433
|
+
>
|
|
434
|
+
<Chat />
|
|
435
|
+
</Replay>
|
|
436
|
+
)
|
|
437
|
+
BeforeFix_DuplicatedToolCalls.decorators = [
|
|
438
|
+
(Story) => (
|
|
439
|
+
<div className="m-auto flex h-screen w-full max-w-3xl flex-col px-4">
|
|
440
|
+
<Story />
|
|
441
|
+
</div>
|
|
442
|
+
),
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* AFTER the fix (AGE-1295): the converter now deduplicates tool calls, so
|
|
447
|
+
* each tool group correctly shows only its own tool call.
|
|
448
|
+
*
|
|
449
|
+
* Expected: each group shows exactly 1 tool call.
|
|
450
|
+
*/
|
|
451
|
+
export const AfterFix_DeduplicatedToolCalls: Story = () => (
|
|
452
|
+
<Replay
|
|
453
|
+
cassette={interleavedToolCallsCassette}
|
|
454
|
+
config={{
|
|
455
|
+
variant: 'standalone',
|
|
456
|
+
tools: { expandToolGroupsByDefault: false },
|
|
457
|
+
}}
|
|
458
|
+
typingSpeed={0}
|
|
459
|
+
userMessageDelay={0}
|
|
460
|
+
assistantStartDelay={0}
|
|
461
|
+
>
|
|
462
|
+
<Chat />
|
|
463
|
+
</Replay>
|
|
464
|
+
)
|
|
465
|
+
AfterFix_DeduplicatedToolCalls.decorators = [
|
|
466
|
+
(Story) => (
|
|
467
|
+
<div className="m-auto flex h-screen w-full max-w-3xl flex-col px-4">
|
|
468
|
+
<Story />
|
|
469
|
+
</div>
|
|
470
|
+
),
|
|
471
|
+
]
|
|
472
|
+
|
|
243
473
|
/**
|
|
244
474
|
* Replay with reasoning (chain-of-thought) content. The assistant's
|
|
245
475
|
* internal reasoning is shown before the final response.
|
|
@@ -69,7 +69,11 @@ export const ShadowRoot = ({
|
|
|
69
69
|
}, [shadowRoot, elementsStyles])
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
|
-
<div
|
|
72
|
+
<div
|
|
73
|
+
ref={hostRef}
|
|
74
|
+
className={hostClassName}
|
|
75
|
+
style={{ isolation: 'isolate', ...hostStyle }}
|
|
76
|
+
>
|
|
73
77
|
{shadowRoot
|
|
74
78
|
? createPortal(
|
|
75
79
|
<div
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { X, Heart } from 'lucide-react'
|
|
2
|
-
import * as m from 'motion/react-m'
|
|
3
|
-
import { useState, type FC } from 'react'
|
|
4
|
-
import { AnimatePresence } from 'motion/react'
|
|
5
|
-
|
|
6
|
-
import { cn } from '@/lib/utils'
|
|
7
|
-
import { EASE_OUT_QUINT } from '@/lib/easing'
|
|
8
1
|
import {
|
|
9
2
|
Tooltip,
|
|
10
3
|
TooltipContent,
|
|
11
4
|
TooltipProvider,
|
|
12
5
|
TooltipTrigger,
|
|
13
6
|
} from '@/components/ui/tooltip'
|
|
7
|
+
import { EASE_OUT_QUINT } from '@/lib/easing'
|
|
8
|
+
import { cn } from '@/lib/utils'
|
|
9
|
+
import { Heart, X } from 'lucide-react'
|
|
10
|
+
import { AnimatePresence } from 'motion/react'
|
|
11
|
+
import * as m from 'motion/react-m'
|
|
12
|
+
import { useState, type FC } from 'react'
|
|
14
13
|
|
|
15
14
|
export type FeedbackType = 'dislike' | 'like'
|
|
16
15
|
|