@gram-ai/elements 1.25.1 → 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/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-C3UbmFRR.cjs → index-CAtaLV1E.cjs} +63 -54
- 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-DxJwZ5Kc.js → index-DLWQ91ow.js} +8439 -8365
- package/dist/index-DLWQ91ow.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-CijCgLrw.js → profiler-BaG0scxd.js} +2 -2
- package/dist/{profiler-CijCgLrw.js.map → profiler-BaG0scxd.js.map} +1 -1
- package/dist/{profiler-DAT0DL1W.cjs → profiler-CuqENACf.cjs} +2 -2
- package/dist/{profiler-DAT0DL1W.cjs.map → profiler-CuqENACf.cjs.map} +1 -1
- package/dist/{startRecording-gmhENmf0.js → startRecording-86bHmd-l.js} +2 -2
- package/dist/{startRecording-gmhENmf0.js.map → startRecording-86bHmd-l.js.map} +1 -1
- package/dist/{startRecording-DotsE8QT.cjs → startRecording-BiLmoqZa.cjs} +2 -2
- package/dist/{startRecording-DotsE8QT.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/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/types/index.ts +4 -4
- package/dist/index-C3UbmFRR.cjs.map +0 -1
- package/dist/index-CtyV0c-T.js.map +0 -1
- package/dist/index-DxJwZ5Kc.js.map +0 -1
- package/dist/index-iUSSoKFz.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
|
},
|
|
@@ -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
|
|
|
@@ -23,10 +23,16 @@ import {
|
|
|
23
23
|
useAssistantState,
|
|
24
24
|
} from '@assistant-ui/react'
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
AnimatePresence,
|
|
28
|
+
LazyMotion,
|
|
29
|
+
MotionConfig,
|
|
30
|
+
domAnimation,
|
|
31
|
+
} from 'motion/react'
|
|
27
32
|
import * as m from 'motion/react-m'
|
|
28
33
|
import {
|
|
29
34
|
createContext,
|
|
35
|
+
useCallback,
|
|
30
36
|
useContext,
|
|
31
37
|
useEffect,
|
|
32
38
|
useMemo,
|
|
@@ -34,7 +40,6 @@ import {
|
|
|
34
40
|
useState,
|
|
35
41
|
type FC,
|
|
36
42
|
} from 'react'
|
|
37
|
-
import { AnimatePresence } from 'motion/react'
|
|
38
43
|
|
|
39
44
|
import {
|
|
40
45
|
ComposerAddAttachment,
|
|
@@ -43,25 +48,27 @@ import {
|
|
|
43
48
|
} from '@/components/assistant-ui/attachment'
|
|
44
49
|
import { FollowOnSuggestions } from '@/components/assistant-ui/follow-on-suggestions'
|
|
45
50
|
import { MarkdownText } from '@/components/assistant-ui/markdown-text'
|
|
51
|
+
import { MentionedToolsBadges } from '@/components/assistant-ui/mentioned-tools-badges'
|
|
46
52
|
import { MessageFeedback } from '@/components/assistant-ui/message-feedback'
|
|
47
53
|
import { Reasoning, ReasoningGroup } from '@/components/assistant-ui/reasoning'
|
|
48
54
|
import { ToolFallback } from '@/components/assistant-ui/tool-fallback'
|
|
49
55
|
import { ToolMentionAutocomplete } from '@/components/assistant-ui/tool-mention-autocomplete'
|
|
50
|
-
import { MentionedToolsBadges } from '@/components/assistant-ui/mentioned-tools-badges'
|
|
51
56
|
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
|
|
52
57
|
import { Button } from '@/components/ui/button'
|
|
53
|
-
import {
|
|
54
|
-
|
|
58
|
+
import { useChatId } from '@/contexts/ChatIdContext'
|
|
59
|
+
import { useReplayContext } from '@/contexts/ReplayContext'
|
|
60
|
+
import { useAuth } from '@/hooks/useAuth'
|
|
55
61
|
import { useDensity } from '@/hooks/useDensity'
|
|
56
62
|
import { useElements } from '@/hooks/useElements'
|
|
63
|
+
import { isLocalThreadId } from '@/hooks/useGramThreadListAdapter'
|
|
57
64
|
import { useRadius } from '@/hooks/useRadius'
|
|
65
|
+
import { useRecordCassette } from '@/hooks/useRecordCassette'
|
|
58
66
|
import { useThemeProps } from '@/hooks/useThemeProps'
|
|
67
|
+
import { useToolMentions } from '@/hooks/useToolMentions'
|
|
68
|
+
import { getApiUrl } from '@/lib/api'
|
|
59
69
|
import { EASE_OUT_QUINT } from '@/lib/easing'
|
|
60
70
|
import { MODELS } from '@/lib/models'
|
|
61
71
|
import { cn } from '@/lib/utils'
|
|
62
|
-
import { useRecordCassette } from '@/hooks/useRecordCassette'
|
|
63
|
-
import { useReplayContext } from '@/contexts/ReplayContext'
|
|
64
|
-
import { ConnectionStatusIndicatorSafe } from './connection-status-indicator'
|
|
65
72
|
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
|
|
66
73
|
import {
|
|
67
74
|
Tooltip,
|
|
@@ -69,8 +76,11 @@ import {
|
|
|
69
76
|
TooltipProvider,
|
|
70
77
|
TooltipTrigger,
|
|
71
78
|
} from '../ui/tooltip'
|
|
79
|
+
import { ConnectionStatusIndicatorSafe } from './connection-status-indicator'
|
|
72
80
|
import { ToolGroup } from './tool-group'
|
|
73
81
|
|
|
82
|
+
type Feedback = 'success' | 'failure'
|
|
83
|
+
|
|
74
84
|
// Context for chat resolution state
|
|
75
85
|
const ChatResolutionContext = createContext<{
|
|
76
86
|
isResolved: boolean
|
|
@@ -78,12 +88,14 @@ const ChatResolutionContext = createContext<{
|
|
|
78
88
|
setResolved: () => void
|
|
79
89
|
setUnresolved: () => void
|
|
80
90
|
resetFeedbackHidden: () => void
|
|
91
|
+
submitFeedback: (feedback: Feedback) => Promise<void>
|
|
81
92
|
}>({
|
|
82
93
|
isResolved: false,
|
|
83
94
|
feedbackHidden: false,
|
|
84
95
|
setResolved: () => {},
|
|
85
96
|
setUnresolved: () => {},
|
|
86
97
|
resetFeedbackHidden: () => {},
|
|
98
|
+
submitFeedback: async () => {},
|
|
87
99
|
})
|
|
88
100
|
|
|
89
101
|
const useChatResolution = () => useContext(ChatResolutionContext)
|
|
@@ -113,9 +125,16 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
|
|
|
113
125
|
const { config } = useElements()
|
|
114
126
|
const components = config.components ?? {}
|
|
115
127
|
const showStaticSessionWarning = config.api && 'sessionToken' in config.api
|
|
116
|
-
const showFeedback = config.thread?.
|
|
128
|
+
const showFeedback = config.thread?.showFeedback ?? false
|
|
117
129
|
const [isResolved, setIsResolved] = useState(false)
|
|
118
130
|
const [feedbackHidden, setFeedbackHidden] = useState(false)
|
|
131
|
+
const chatId = useChatId()
|
|
132
|
+
|
|
133
|
+
const apiUrl = getApiUrl(config)
|
|
134
|
+
const auth = useAuth({
|
|
135
|
+
auth: config.api,
|
|
136
|
+
projectSlug: config.projectSlug,
|
|
137
|
+
})
|
|
119
138
|
|
|
120
139
|
const setResolved = () => setIsResolved(true)
|
|
121
140
|
const setUnresolved = () => {
|
|
@@ -124,6 +143,38 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
|
|
|
124
143
|
}
|
|
125
144
|
const resetFeedbackHidden = () => setFeedbackHidden(false)
|
|
126
145
|
|
|
146
|
+
// Submit feedback to the API
|
|
147
|
+
const submitFeedback = useCallback(
|
|
148
|
+
async (feedback: Feedback) => {
|
|
149
|
+
if (!chatId) return
|
|
150
|
+
if (isLocalThreadId(chatId)) {
|
|
151
|
+
console.error("Local thread ID, can't submit feedback")
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch(`${apiUrl}/rpc/chat.submitFeedback`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'application/json',
|
|
160
|
+
...auth.headers,
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({
|
|
163
|
+
id: chatId,
|
|
164
|
+
feedback,
|
|
165
|
+
}),
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
console.error('Failed to submit feedback:', response.statusText)
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('Failed to submit feedback:', error)
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
[chatId, apiUrl, auth.headers]
|
|
176
|
+
)
|
|
177
|
+
|
|
127
178
|
return (
|
|
128
179
|
<ChatResolutionContext.Provider
|
|
129
180
|
value={{
|
|
@@ -132,6 +183,7 @@ export const Thread: FC<ThreadProps> = ({ className }) => {
|
|
|
132
183
|
setResolved,
|
|
133
184
|
setUnresolved,
|
|
134
185
|
resetFeedbackHidden,
|
|
186
|
+
submitFeedback,
|
|
135
187
|
}}
|
|
136
188
|
>
|
|
137
189
|
<LazyMotion features={domAnimation}>
|
|
@@ -448,7 +500,16 @@ const FeedbackHiddenResetter: FC = () => {
|
|
|
448
500
|
}
|
|
449
501
|
|
|
450
502
|
const ComposerFeedback: FC = () => {
|
|
451
|
-
const { isResolved, feedbackHidden, setResolved } =
|
|
503
|
+
const { isResolved, feedbackHidden, setResolved, submitFeedback } =
|
|
504
|
+
useChatResolution()
|
|
505
|
+
|
|
506
|
+
const handleFeedback = useCallback(
|
|
507
|
+
async (type: 'like' | 'dislike') => {
|
|
508
|
+
const feedback = type === 'like' ? 'success' : 'failure'
|
|
509
|
+
await submitFeedback(feedback)
|
|
510
|
+
},
|
|
511
|
+
[submitFeedback]
|
|
512
|
+
)
|
|
452
513
|
|
|
453
514
|
return (
|
|
454
515
|
<ThreadPrimitive.If empty={false}>
|
|
@@ -466,7 +527,11 @@ const ComposerFeedback: FC = () => {
|
|
|
466
527
|
transition={{ duration: 0.2, ease: EASE_OUT_QUINT }}
|
|
467
528
|
className="mb-3"
|
|
468
529
|
>
|
|
469
|
-
<MessageFeedback
|
|
530
|
+
<MessageFeedback
|
|
531
|
+
className="mx-auto"
|
|
532
|
+
onResolved={setResolved}
|
|
533
|
+
onFeedback={handleFeedback}
|
|
534
|
+
/>
|
|
470
535
|
</m.div>
|
|
471
536
|
)}
|
|
472
537
|
</AnimatePresence>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface ChatIdContextValue {
|
|
4
|
+
chatId: string | null
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const ChatIdContext = createContext<ChatIdContextValue | null>(null)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to access the current chat ID from the Elements context.
|
|
11
|
+
* Works in both history-enabled and history-disabled modes.
|
|
12
|
+
*
|
|
13
|
+
* @returns The current chat ID, or null if not yet initialized
|
|
14
|
+
*/
|
|
15
|
+
export const useChatId = () => {
|
|
16
|
+
const context = useContext(ChatIdContext)
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error('useChatId must be used within ElementsProvider')
|
|
19
|
+
}
|
|
20
|
+
return context.chatId
|
|
21
|
+
}
|