Skip to content

Commit 8b29820

Browse files
committed
Added chat first message
1 parent 60a938b commit 8b29820

File tree

12 files changed

+119
-58
lines changed

12 files changed

+119
-58
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ The simplest way to add the widget to your website:
8484
| `emptyVoiceActiveMessage` | `string` | - | Message during active voice call |
8585
| `emptyChatMessage` | `string` | - | Message when chat is empty |
8686
| `emptyHybridMessage` | `string` | - | Message for hybrid mode |
87+
| `firstChatMessage` | `string` | - | Initial assistant message in chat |
8788
| `requireConsent` | `boolean` | `false` | Show consent form before first use |
88-
| `termsContent` | `string` | - | Custom consent form text |
89-
| `localStorageKey` | `string` | `'vapi_widget_consent'` | Key for storing consent |
90-
| `showTranscript` | `boolean` | `true` | Show/hide voice transcript |
89+
| `termsContent` | `string` | _(default message)_ | Terms & conditions text |
90+
| `localStorageKey` | `string` | `"vapi_widget_consent"` | Key for storing consent |
91+
| `showTranscript` | `boolean` | `false` | Show/hide voice transcript |
9192

9293
### Event Callbacks
9394

@@ -133,6 +134,7 @@ Use the widget as a custom HTML element with kebab-case attributes - the cleanes
133134
theme="dark"
134135
accent-color="#8B5CF6"
135136
require-consent="true"
137+
first-chat-message="Welcome! How can I assist you?"
136138
></vapi-widget>
137139
```
138140

@@ -174,6 +176,7 @@ Use this approach if your environment doesn't support custom elements or for bet
174176
mode="chat"
175177
theme="dark"
176178
accentColor="#8B5CF6"
179+
firstChatMessage="Hello! How can I help you today?"
177180
/>
178181
```
179182

example/src/App.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import VapiConfigurationSection from './components/builder/VapiConfigurationSect
1818

1919
function App() {
2020
const [config, setConfig] = useState<WidgetConfig>({
21-
mode: 'voice',
21+
mode: 'chat',
2222
theme: 'light',
2323
// Default colors matching VapiWidget defaults
2424
baseColor: '#ffffff', // Light mode default (automatically switches to #000000 in dark mode)
@@ -35,7 +35,8 @@ function App() {
3535
termsContent:
3636
'By clicking "Agree," and each time I interact with this AI agent, I consent to the recording, storage, and sharing of my communications with third-party service providers, and as otherwise described in our Terms of Service.',
3737
localStorageKey: 'vapi_widget_consent',
38-
showTranscript: true,
38+
showTranscript: false,
39+
firstChatMessage: 'Hey, How can I help you today?',
3940
// Vapi Configuration
4041
publicKey: import.meta.env.VITE_VAPI_API_KEY || 'your-vapi-public-key',
4142
assistantId: import.meta.env.VITE_VAPI_ASSISTANT_ID || 'demo-assistant-id',
@@ -75,6 +76,9 @@ function App() {
7576
`require-consent="${config.requireConsent}"`,
7677
`local-storage-key="${config.localStorageKey}"`,
7778
`show-transcript="${config.showTranscript}"`,
79+
config.firstChatMessage
80+
? `first-chat-message="${config.firstChatMessage}"`
81+
: null,
7882
]
7983
.filter(Boolean)
8084
.join(' ');
@@ -187,6 +191,7 @@ function App() {
187191
termsContent={config.termsContent}
188192
localStorageKey={config.localStorageKey}
189193
showTranscript={config.showTranscript}
194+
firstChatMessage={config.firstChatMessage}
190195
onCallStart={() => console.log('Call started')}
191196
onCallEnd={() => console.log('Call ended')}
192197
onMessage={(message) => console.log('Message:', message)}

example/src/components/builder/ModeSection.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,57 +36,57 @@ const ModeSection: React.FC<ModeSectionProps> = ({
3636
<div className="grid grid-cols-2 gap-4">
3737
<div
3838
className={`p-4 rounded-lg border-2 cursor-pointer transition-all ${
39-
mode === 'voice'
39+
mode === 'chat'
4040
? 'border-teal-500 bg-teal-50'
4141
: 'border-gray-300 hover:border-gray-400'
4242
}`}
43-
onClick={() => onModeChange('voice')}
43+
onClick={() => onModeChange('chat')}
4444
>
4545
<div className="flex items-center space-x-3">
4646
<div
4747
className={`w-10 h-10 rounded-lg flex items-center justify-center ${
48-
mode === 'voice' ? 'bg-teal-500' : 'bg-gray-200'
48+
mode === 'chat' ? 'bg-teal-500' : 'bg-gray-200'
4949
}`}
5050
>
51-
<MicrophoneIcon
51+
<ChatCircleIcon
5252
size={20}
5353
weight="fill"
54-
className={mode === 'voice' ? 'text-white' : 'text-gray-500'}
54+
className={mode === 'chat' ? 'text-white' : 'text-gray-500'}
5555
/>
5656
</div>
5757
<div>
58-
<h3 className="font-medium text-gray-900">Voice</h3>
58+
<h3 className="font-medium text-gray-900">Chat</h3>
5959
<p className="text-sm text-gray-600">
60-
Users will speak with the widget
60+
Users will text with the widget
6161
</p>
6262
</div>
6363
</div>
6464
</div>
6565

6666
<div
6767
className={`p-4 rounded-lg border-2 cursor-pointer transition-all ${
68-
mode === 'chat'
68+
mode === 'voice'
6969
? 'border-teal-500 bg-teal-50'
7070
: 'border-gray-300 hover:border-gray-400'
7171
}`}
72-
onClick={() => onModeChange('chat')}
72+
onClick={() => onModeChange('voice')}
7373
>
7474
<div className="flex items-center space-x-3">
7575
<div
7676
className={`w-10 h-10 rounded-lg flex items-center justify-center ${
77-
mode === 'chat' ? 'bg-teal-500' : 'bg-gray-200'
77+
mode === 'voice' ? 'bg-teal-500' : 'bg-gray-200'
7878
}`}
7979
>
80-
<ChatCircleIcon
80+
<MicrophoneIcon
8181
size={20}
8282
weight="fill"
83-
className={mode === 'chat' ? 'text-white' : 'text-gray-500'}
83+
className={mode === 'voice' ? 'text-white' : 'text-gray-500'}
8484
/>
8585
</div>
8686
<div>
87-
<h3 className="font-medium text-gray-900">Chat</h3>
87+
<h3 className="font-medium text-gray-900">Voice</h3>
8888
<p className="text-sm text-gray-600">
89-
Users will text with the widget
89+
Users will speak with the widget
9090
</p>
9191
</div>
9292
</div>

example/src/components/builder/VapiConfigurationSection.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@ const VapiConfigurationSection: React.FC<VapiConfigurationSectionProps> = ({
131131
Complete assistant configuration object (only used for voice calls)
132132
</p>
133133
</div>
134+
135+
{/* First Chat Message */}
136+
<div>
137+
<label className="block text-sm font-medium mb-2 text-gray-700">
138+
First Chat Message
139+
<span className="text-xs text-gray-500 ml-2">(Chat Only)</span>
140+
</label>
141+
<input
142+
type="text"
143+
value={config.firstChatMessage || ''}
144+
onChange={(e) => updateConfig('firstChatMessage', e.target.value)}
145+
className="w-full p-2 rounded-md border bg-white border-gray-300 text-gray-900 text-sm"
146+
placeholder="Hey, How can I help you today?"
147+
/>
148+
<p className="text-xs mt-1 text-gray-500">
149+
The initial message displayed when the chat opens
150+
</p>
151+
</div>
134152
</div>
135153
</div>
136154
);

example/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface WidgetConfig {
1515
termsContent: string;
1616
localStorageKey: string;
1717
showTranscript: boolean;
18+
firstChatMessage?: string;
1819

1920
// Vapi Configuration
2021
apiUrl?: string;

src/components/VapiWidget.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ const VapiWidget: React.FC<VapiWidgetProps> = ({
3939
emptyVoiceActiveMessage = 'Listening...',
4040
emptyChatMessage = 'Type a message to start chatting',
4141
emptyHybridMessage = 'Use voice or text to communicate',
42+
firstChatMessage,
4243
requireConsent = false,
4344
termsContent = 'By clicking "Agree," and each time I interact with this AI agent, I consent to the recording, storage, and sharing of my communications with third-party service providers, and as otherwise described in our Terms of Service.',
4445
localStorageKey = 'vapi_widget_consent',
45-
showTranscript = true,
46+
showTranscript = false,
4647
onCallStart,
4748
onCallEnd,
4849
onMessage,
@@ -62,6 +63,7 @@ const VapiWidget: React.FC<VapiWidgetProps> = ({
6263
assistant,
6364
assistantOverrides,
6465
apiUrl,
66+
firstChatMessage,
6567
onCallStart,
6668
onCallEnd,
6769
onMessage,

src/components/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export interface VapiWidgetProps {
3434
emptyChatMessage?: string;
3535
emptyHybridMessage?: string;
3636

37+
// Initial Chat Message
38+
firstChatMessage?: string;
39+
3740
// Legal & Consent
3841
requireConsent?: boolean;
3942
termsContent?: string;

src/hooks/useVapiChat.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface UseVapiChatOptions {
3232
assistantOverrides?: AssistantOverrides;
3333
apiUrl?: string;
3434
sessionId?: string;
35+
firstChatMessage?: string;
3536
onMessage?: (message: ChatMessage) => void;
3637
onError?: (error: Error) => void;
3738
}
@@ -167,11 +168,23 @@ export const useVapiChat = ({
167168
assistantOverrides,
168169
apiUrl,
169170
sessionId: initialSessionId,
171+
firstChatMessage,
170172
onMessage,
171173
onError,
172174
}: UseVapiChatOptions): VapiChatState &
173175
VapiChatHandlers & { isEnabled: boolean } => {
174-
const [messages, setMessages] = useState<ChatMessage[]>([]);
176+
const [messages, setMessages] = useState<ChatMessage[]>(() => {
177+
if (enabled && firstChatMessage) {
178+
return [
179+
{
180+
role: 'assistant',
181+
content: firstChatMessage,
182+
timestamp: new Date(),
183+
},
184+
];
185+
}
186+
return [];
187+
});
175188
const [isTyping, setIsTyping] = useState(false);
176189
const [isLoading, setIsLoading] = useState(false);
177190
const [sessionId, setSessionId] = useState<string | undefined>(
@@ -258,9 +271,30 @@ export const useVapiChat = ({
258271
onMessage
259272
);
260273

274+
let input: string | Array<{ role: string; content: string }>;
275+
if (
276+
firstChatMessage &&
277+
firstChatMessage.trim() !== '' &&
278+
messages.length === 1 &&
279+
messages[0].role === 'assistant'
280+
) {
281+
input = [
282+
{
283+
role: 'assistant',
284+
content: firstChatMessage,
285+
},
286+
{
287+
role: 'user',
288+
content: text.trim(),
289+
},
290+
];
291+
} else {
292+
input = text.trim();
293+
}
294+
261295
const abort = await clientRef.current!.streamChat(
262296
{
263-
input: text.trim(),
297+
input,
264298
assistantId: assistantId!,
265299
assistantOverrides,
266300
sessionId,
@@ -291,11 +325,24 @@ export const useVapiChat = ({
291325
addMessage,
292326
onError,
293327
onMessage,
328+
firstChatMessage,
329+
messages,
294330
]
295331
);
296332

297333
const clearMessages = useCallback(() => {
298-
setMessages([]);
334+
// Reset to firstChatMessage if provided, otherwise empty array
335+
if (enabled && firstChatMessage) {
336+
setMessages([
337+
{
338+
role: 'assistant',
339+
content: firstChatMessage,
340+
timestamp: new Date(),
341+
},
342+
]);
343+
} else {
344+
setMessages([]);
345+
}
299346

300347
// Abort any ongoing stream
301348
abortFnRef.current?.();
@@ -310,7 +357,7 @@ export const useVapiChat = ({
310357

311358
// Clear sessionId when clearing messages to start fresh
312359
setSessionId(undefined);
313-
}, []);
360+
}, [enabled, firstChatMessage]);
314361

315362
return {
316363
// State

src/hooks/useVapiWidget.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface UseVapiWidgetOptions {
1414
assistant?: any;
1515
assistantOverrides?: AssistantOverrides;
1616
apiUrl?: string;
17+
firstChatMessage?: string;
1718
onCallStart?: () => void;
1819
onCallEnd?: () => void;
1920
onMessage?: (message: any) => void;
@@ -27,6 +28,7 @@ export const useVapiWidget = ({
2728
assistant,
2829
assistantOverrides,
2930
apiUrl,
31+
firstChatMessage,
3032
onCallStart,
3133
onCallEnd,
3234
onMessage,
@@ -97,6 +99,7 @@ export const useVapiWidget = ({
9799
apiUrl,
98100
onMessage, // Keep the callback for external notifications
99101
onError,
102+
firstChatMessage,
100103
} as UseVapiChatOptions);
101104

102105
// Combine voice and chat conversations

src/utils/vapiChatClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface AssistantOverrides {
77
}
88

99
export interface VapiChatMessage {
10-
input: string;
10+
input: string | Array<{ role: string; content: string }>;
1111
assistantId: string;
1212
assistantOverrides?: AssistantOverrides;
1313
sessionId?: string;

0 commit comments

Comments
 (0)