Skip to content

Commit 1de20b6

Browse files
committed
Add cta-title/subtitle to vapi custom widget
1 parent 517d9cc commit 1de20b6

File tree

6 files changed

+95
-4
lines changed

6 files changed

+95
-4
lines changed

example/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ function App() {
2929
size: 'full',
3030
position: 'bottom-right',
3131
title: 'TALK WITH AI',
32+
ctaTitle: 'Chat with Support',
33+
ctaSubtitle: 'We\'re here to help',
3234
startButtonText: 'Start',
3335
endButtonText: 'End Call',
3436
consentRequired: true,
@@ -72,6 +74,8 @@ function App() {
7274
`size="${config.size}"`,
7375
`position="${config.position}"`,
7476
`title="${config.title}"`,
77+
config.ctaTitle ? `cta-title="${config.ctaTitle}"` : null,
78+
config.ctaSubtitle ? `cta-subtitle="${config.ctaSubtitle}"` : null,
7579
`start-button-text="${config.startButtonText}"`,
7680
config.endButtonText ? `end-button-text="${config.endButtonText}"` : null,
7781
`consent-required="${config.consentRequired}"`,
@@ -196,6 +200,8 @@ function App() {
196200
consentStorageKey={config.consentStorageKey}
197201
voiceShowTranscript={config.voiceShowTranscript}
198202
chatFirstMessage={config.chatFirstMessage}
203+
ctaTitle={config.ctaTitle}
204+
ctaSubtitle={config.ctaSubtitle}
199205
onVoiceStart={() => console.log('Call started')}
200206
onVoiceEnd={() => console.log('Call ended')}
201207
onMessage={(message) => console.log('Message:', message)}

example/src/components/WidgetPreview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ const WidgetPreview: React.FC<WidgetPreviewProps> = ({ config }) => {
7676
radius={config.radius}
7777
size={config.size}
7878
title={config.title}
79+
ctaTitle={config.ctaTitle}
80+
ctaSubtitle={config.ctaSubtitle}
7981
startButtonText={config.startButtonText}
8082
endButtonText={config.endButtonText}
8183
consentRequired={config.consentRequired}

example/src/components/builder/TextLabelsSection.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,46 @@ const TextLabelsSection: React.FC<TextLabelsSectionProps> = ({
4343
/>
4444
</div>
4545

46+
<div>
47+
<label className="block text-sm font-medium mb-2 text-gray-700">
48+
CTA Title (Optional)
49+
<svg
50+
className="w-3 h-3 inline ml-1 text-gray-400"
51+
fill="currentColor"
52+
viewBox="0 0 24 24"
53+
>
54+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" />
55+
</svg>
56+
</label>
57+
<input
58+
type="text"
59+
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
60+
value={config.ctaTitle || ''}
61+
onChange={(e) => updateConfig('ctaTitle', e.target.value)}
62+
placeholder="Defaults to Main Label"
63+
/>
64+
</div>
65+
66+
<div>
67+
<label className="block text-sm font-medium mb-2 text-gray-700">
68+
CTA Subtitle (Optional)
69+
<svg
70+
className="w-3 h-3 inline ml-1 text-gray-400"
71+
fill="currentColor"
72+
viewBox="0 0 24 24"
73+
>
74+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" />
75+
</svg>
76+
</label>
77+
<input
78+
type="text"
79+
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
80+
value={config.ctaSubtitle || ''}
81+
onChange={(e) => updateConfig('ctaSubtitle', e.target.value)}
82+
placeholder="Add a subtitle to the floating button"
83+
/>
84+
</div>
85+
4686
<div>
4787
<label className="block text-sm font-medium mb-2 text-gray-700">
4888
Start Button Text

example/src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface WidgetConfig {
1414
| 'top-left'
1515
| 'bottom-center';
1616
title: string;
17+
ctaTitle?: string;
18+
ctaSubtitle?: string;
1719
startButtonText: string;
1820
endButtonText: string;
1921
consentRequired: boolean;

src/widget/index.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,40 +130,80 @@ function initializeWidgets() {
130130
const htmlElement = element as HTMLElement;
131131
const props: any = {};
132132

133+
// Note: Event handlers (onVoiceStart, onVoiceEnd, onMessage, onError) and their deprecated versions
134+
// cannot be passed via HTML attributes. They must be set programmatically or via React props.
133135
const attributeMap: Record<string, string> = {
136+
// Mode & Theme
134137
mode: 'mode',
135138
theme: 'theme',
136-
radius: 'radius',
137-
size: 'size',
139+
140+
// Layout & Position
138141
position: 'position',
139-
title: 'title',
142+
size: 'size',
143+
'border-radius': 'borderRadius',
144+
radius: 'radius', // deprecated alias
145+
146+
// Colors
140147
'base-bg-color': 'baseBgColor',
141148
'accent-color': 'accentColor',
142149
'cta-button-color': 'ctaButtonColor',
143150
'cta-button-text-color': 'ctaButtonTextColor',
151+
152+
// Text & Labels
153+
title: 'title',
154+
'cta-title': 'ctaTitle',
155+
'cta-subtitle': 'ctaSubtitle',
144156
'start-button-text': 'startButtonText',
145157
'end-button-text': 'endButtonText',
158+
159+
// Empty State Messages
146160
'voice-empty-message': 'voiceEmptyMessage',
147161
'voice-active-empty-message': 'voiceActiveEmptyMessage',
148162
'chat-empty-message': 'chatEmptyMessage',
149163
'hybrid-empty-message': 'hybridEmptyMessage',
164+
165+
// Chat Configuration
150166
'chat-first-message': 'chatFirstMessage',
151167
'chat-placeholder': 'chatPlaceholder',
168+
169+
// Voice Configuration
152170
'voice-show-transcript': 'voiceShowTranscript',
171+
172+
// Consent Configuration
153173
'consent-required': 'consentRequired',
154174
'consent-title': 'consentTitle',
155175
'consent-content': 'consentContent',
156176
'consent-storage-key': 'consentStorageKey',
177+
178+
// API Configuration
179+
'api-url': 'apiUrl',
180+
157181
// Vapi Configuration
158182
'public-key': 'publicKey',
159183
'assistant-id': 'assistantId',
160184
'assistant-overrides': 'assistantOverrides',
161185
assistant: 'assistant',
186+
187+
// Deprecated properties
188+
'base-color': 'baseColor',
189+
'button-base-color': 'buttonBaseColor',
190+
'button-accent-color': 'buttonAccentColor',
191+
'main-label': 'mainLabel',
192+
'empty-voice-message': 'emptyVoiceMessage',
193+
'empty-voice-active-message': 'emptyVoiceActiveMessage',
194+
'empty-chat-message': 'emptyChatMessage',
195+
'empty-hybrid-message': 'emptyHybridMessage',
196+
'first-chat-message': 'firstChatMessage',
197+
'show-transcript': 'showTranscript',
198+
'require-consent': 'requireConsent',
199+
'terms-content': 'termsContent',
200+
'local-storage-key': 'localStorageKey',
162201
};
163202

164203
Object.entries(attributeMap).forEach(([htmlAttr, propName]) => {
165204
const value = htmlElement.getAttribute(htmlAttr);
166205
if (value !== null) {
206+
// Special handling for JSON attributes
167207
if (propName === 'assistantOverrides' || propName === 'assistant') {
168208
try {
169209
props[propName] = JSON.parse(value);

test-widget-embed.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ <h1>VapiWidget Embed Test</h1>
4444
size="full"
4545
position="bottom-left"
4646
title="Vapi AI"
47-
cta-title="Talk with AI"
47+
cta-title="CTA Title"
48+
cta-subtitle="CTA Subtitle"
4849
start-button-text="Call"
4950
end-button-text="Hang Up"
5051
consent-required="true"

0 commit comments

Comments
 (0)