vibefast-cli 0.1.1
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/FINAL-STATUS.md +144 -0
- package/HOW-IT-WORKS.md +559 -0
- package/PLAN.md +453 -0
- package/README.md +129 -0
- package/RECIPES-READY.md +172 -0
- package/STATUS.md +199 -0
- package/SUCCESS.md +259 -0
- package/TESTING-CHECKLIST.md +450 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/64907821e2634080acce34618d2f3d4c/blobs/11f2769953c717e188062bc644da97c1fd1e4d6d0813a226ce7567dba759afab0000019a736fb8d4 +1 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/vibefast-recipes/blobs/620e8cf7c35d9806da25dee237e1d7e8b2432bd98f755b60e2c7f08a48d2c7b90000019a73736484 +0 -0
- package/cloudflare-worker/MIGRATION.md +160 -0
- package/cloudflare-worker/QUICKSTART.md +200 -0
- package/cloudflare-worker/README.md +242 -0
- package/cloudflare-worker/generate-token.js +32 -0
- package/cloudflare-worker/mini-native@latest.zip +0 -0
- package/cloudflare-worker/setup.sh +143 -0
- package/cloudflare-worker/test-recipe/apps/native/src/app/mini/index.tsx +15 -0
- package/cloudflare-worker/test-recipe/recipe.json +16 -0
- package/cloudflare-worker/worker.js +308 -0
- package/cloudflare-worker/wrangler.toml +13 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +149 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/devices.d.ts +3 -0
- package/dist/commands/devices.d.ts.map +1 -0
- package/dist/commands/devices.js +35 -0
- package/dist/commands/devices.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +67 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +40 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +23 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +16 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +67 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/core/__tests__/journal.test.d.ts +2 -0
- package/dist/core/__tests__/journal.test.d.ts.map +1 -0
- package/dist/core/__tests__/journal.test.js +101 -0
- package/dist/core/__tests__/journal.test.js.map +1 -0
- package/dist/core/__tests__/validate.test.d.ts +2 -0
- package/dist/core/__tests__/validate.test.d.ts.map +1 -0
- package/dist/core/__tests__/validate.test.js +53 -0
- package/dist/core/__tests__/validate.test.js.map +1 -0
- package/dist/core/archive.d.ts +2 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +59 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/auth.d.ts +15 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +76 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/codemod.d.ts +20 -0
- package/dist/core/codemod.d.ts.map +1 -0
- package/dist/core/codemod.js +150 -0
- package/dist/core/codemod.js.map +1 -0
- package/dist/core/fsx.d.ts +12 -0
- package/dist/core/fsx.d.ts.map +1 -0
- package/dist/core/fsx.js +70 -0
- package/dist/core/fsx.js.map +1 -0
- package/dist/core/http.d.ts +30 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +95 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/journal.d.ts +18 -0
- package/dist/core/journal.d.ts.map +1 -0
- package/dist/core/journal.js +34 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/log.d.ts +8 -0
- package/dist/core/log.d.ts.map +1 -0
- package/dist/core/log.js +9 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/pathGuard.d.ts +3 -0
- package/dist/core/pathGuard.d.ts.map +1 -0
- package/dist/core/pathGuard.js +18 -0
- package/dist/core/pathGuard.js.map +1 -0
- package/dist/core/paths.d.ts +11 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +22 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/validate.d.ts +8 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +27 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/docs/decisions.md +55 -0
- package/package.json +39 -0
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder/recipe.json +22 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
- package/recipes/charts/apps/native/src/features/charts/README.md +185 -0
- package/recipes/charts/apps/native/src/features/charts/app/preview.tsx +223 -0
- package/recipes/charts/apps/native/src/features/charts/components/area-chart.tsx +40 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +196 -0
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +65 -0
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +246 -0
- package/recipes/charts/apps/native/src/features/charts/components/index.ts +10 -0
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +308 -0
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +180 -0
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +188 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-area-chart.tsx +265 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +322 -0
- package/recipes/charts/apps/native/src/features/charts/data/mock-data.ts +183 -0
- package/recipes/charts/apps/native/src/features/charts/types/index.ts +66 -0
- package/recipes/charts/recipe.json +22 -0
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/app/index.tsx +302 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-input-bar.tsx +469 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +246 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/image-preview-list.tsx +115 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-list.tsx +173 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +143 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-config.ts +664 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +359 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-conversation.ts +79 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +207 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/file-uploader.ts +238 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/message-handler-service.ts +180 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/types/index.ts +60 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot/recipe.json +22 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/_layout.tsx +25 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +237 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/gallery-image.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +210 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +93 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-gallery-service.ts +98 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-save-service.ts +121 -0
- package/recipes/image-generator/recipe.json +22 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
- package/recipes/quiz/apps/native/src/features/quiz/components/question.tsx +67 -0
- package/recipes/quiz/apps/native/src/features/quiz/config.ts +11 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +133 -0
- package/recipes/quiz/recipe.json +22 -0
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +108 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/animated-number.tsx +102 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/calorie-card.tsx +66 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/circular-progress.tsx +97 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/floating-add-button.tsx +27 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/macro-card.tsx +80 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/promo-banner.tsx +98 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/recently-logged.tsx +64 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/week-calendar.tsx +68 -0
- package/recipes/tracker-app/recipe.json +22 -0
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/upload-all.sh +32 -0
- package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +27 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/README.md +185 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/conversation-status.tsx +76 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/index.ts +4 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/message-input.tsx +98 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-bot-screen.tsx +173 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-controls.tsx +73 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/index.ts +3 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/index.ts +1 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/use-voice-bot.ts +161 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/types.ts +29 -0
- package/recipes/voice-bot/recipe.json +22 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/scripts/create-recipes.mjs +189 -0
- package/src/commands/add.ts +183 -0
- package/src/commands/devices.ts +38 -0
- package/src/commands/doctor.ts +67 -0
- package/src/commands/list.ts +45 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/logout.ts +15 -0
- package/src/commands/remove.ts +78 -0
- package/src/core/__tests__/journal.test.ts +119 -0
- package/src/core/__tests__/validate.test.ts +64 -0
- package/src/core/archive.ts +69 -0
- package/src/core/auth.ts +103 -0
- package/src/core/codemod.ts +211 -0
- package/src/core/fsx.ts +80 -0
- package/src/core/http.ts +136 -0
- package/src/core/journal.ts +64 -0
- package/src/core/log.ts +9 -0
- package/src/core/pathGuard.ts +22 -0
- package/src/core/paths.ts +33 -0
- package/src/core/validate.ts +44 -0
- package/src/index.ts +27 -0
- package/test-critical-cases.mjs +258 -0
- package/tsconfig.json +21 -0
- package/vitest.config.mts +12 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Voice Bot Feature
|
|
2
|
+
|
|
3
|
+
A real-time voice conversation feature powered by ElevenLabs Conversational AI SDK.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Real-time Voice Conversations**: Have natural voice conversations with AI
|
|
8
|
+
- **Text Messaging**: Send text messages and contextual updates during conversations
|
|
9
|
+
- **Feedback System**: Provide feedback on AI responses with like/dislike buttons
|
|
10
|
+
- **Connection Status**: Visual indicators for connection status and speaking state
|
|
11
|
+
- **Auto-Cleanup**: Automatically ends conversations when user leaves the screen to prevent resource waste
|
|
12
|
+
- **VibeFast Integration**: Fully integrated with VibeFast theming and component system
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
### 1. ElevenLabs Account Setup
|
|
17
|
+
|
|
18
|
+
1. Go to [ElevenLabs](https://elevenlabs.io/)
|
|
19
|
+
2. Create an account or sign in
|
|
20
|
+
3. Navigate to **Conversational AI** section
|
|
21
|
+
4. Create a new agent
|
|
22
|
+
5. Copy the agent ID from your agent settings
|
|
23
|
+
|
|
24
|
+
### 2. Environment Configuration
|
|
25
|
+
|
|
26
|
+
Add your ElevenLabs agent ID to your environment file:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# .env.local
|
|
30
|
+
ELEVENLABS_AGENT_ID=your_agent_id_here
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. Required Dependencies
|
|
34
|
+
|
|
35
|
+
The following dependencies are automatically installed:
|
|
36
|
+
|
|
37
|
+
- `@elevenlabs/react-native` - ElevenLabs React Native SDK
|
|
38
|
+
- `@livekit/react-native-expo-plugin` - WebRTC support for Expo
|
|
39
|
+
- `@config-plugins/react-native-webrtc` - WebRTC configuration plugin
|
|
40
|
+
|
|
41
|
+
### 4. Permissions
|
|
42
|
+
|
|
43
|
+
The app.config.ts has been updated with the required permissions:
|
|
44
|
+
|
|
45
|
+
**iOS:**
|
|
46
|
+
|
|
47
|
+
- `NSMicrophoneUsageDescription` - For voice input
|
|
48
|
+
|
|
49
|
+
**Android:**
|
|
50
|
+
|
|
51
|
+
- `android.permission.RECORD_AUDIO` - For voice input
|
|
52
|
+
- `android.permission.ACCESS_NETWORK_STATE` - For network connectivity
|
|
53
|
+
- `android.permission.INTERNET` - For internet access
|
|
54
|
+
- `android.permission.MODIFY_AUDIO_SETTINGS` - For audio configuration
|
|
55
|
+
- `android.permission.BLUETOOTH` - For Bluetooth audio devices
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### Basic Implementation
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { VoiceBotScreen } from '@/features/voice-bot';
|
|
63
|
+
|
|
64
|
+
function MyVoiceBotScreen() {
|
|
65
|
+
return (
|
|
66
|
+
<VoiceBotScreen
|
|
67
|
+
config={{ agentId: 'your-agent-id' }}
|
|
68
|
+
title="Voice Assistant"
|
|
69
|
+
subtitle="Start a conversation with your AI assistant"
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Custom Hook Usage
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useVoiceBot } from '@/features/voice-bot';
|
|
79
|
+
|
|
80
|
+
function CustomVoiceBotComponent() {
|
|
81
|
+
const { state, actions, textInput, setTextInput } = useVoiceBot(
|
|
82
|
+
{ agentId: 'your-agent-id' },
|
|
83
|
+
{
|
|
84
|
+
onConnect: (conversationId) => {
|
|
85
|
+
console.log('Connected:', conversationId);
|
|
86
|
+
},
|
|
87
|
+
onMessage: (message) => {
|
|
88
|
+
console.log('New message:', message);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<View>
|
|
95
|
+
<Text>Status: {state.status}</Text>
|
|
96
|
+
<Button
|
|
97
|
+
label="Start Conversation"
|
|
98
|
+
onPress={actions.startConversation}
|
|
99
|
+
disabled={state.status !== 'disconnected'}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Components
|
|
107
|
+
|
|
108
|
+
### VoiceBotScreen
|
|
109
|
+
|
|
110
|
+
Main screen component that provides a complete voice bot interface.
|
|
111
|
+
|
|
112
|
+
**Props:**
|
|
113
|
+
|
|
114
|
+
- `config: VoiceBotConfig` - Configuration object with agent ID
|
|
115
|
+
- `title?: string` - Screen title (optional)
|
|
116
|
+
- `subtitle?: string` - Screen subtitle (optional)
|
|
117
|
+
|
|
118
|
+
### ConversationStatusIndicator
|
|
119
|
+
|
|
120
|
+
Displays the current connection status and conversation information.
|
|
121
|
+
|
|
122
|
+
### VoiceControls
|
|
123
|
+
|
|
124
|
+
Provides start/stop conversation buttons and feedback controls.
|
|
125
|
+
|
|
126
|
+
### MessageInput
|
|
127
|
+
|
|
128
|
+
Text input component for sending messages and contextual updates.
|
|
129
|
+
|
|
130
|
+
## Development Build Required
|
|
131
|
+
|
|
132
|
+
**Important**: This feature requires a development build and cannot run in Expo Go due to WebRTC native dependencies.
|
|
133
|
+
|
|
134
|
+
### Building for Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Prebuild (required for native dependencies)
|
|
138
|
+
pnpx expo prebuild
|
|
139
|
+
|
|
140
|
+
# iOS
|
|
141
|
+
pnpm ios -d
|
|
142
|
+
|
|
143
|
+
# Android
|
|
144
|
+
pnpm android -d
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Simulator Audio Settings
|
|
148
|
+
|
|
149
|
+
When running on simulators, adjust audio settings:
|
|
150
|
+
|
|
151
|
+
**iOS Simulator:**
|
|
152
|
+
|
|
153
|
+
- In I/O menu, set correct audio input/output devices
|
|
154
|
+
- Increase volume (defaults to 0)
|
|
155
|
+
|
|
156
|
+
**Android Emulator:**
|
|
157
|
+
|
|
158
|
+
- In Extended Controls, enable "Virtual microphone uses host audio input"
|
|
159
|
+
|
|
160
|
+
## Architecture
|
|
161
|
+
|
|
162
|
+
The voice bot feature follows VibeFast architecture patterns:
|
|
163
|
+
|
|
164
|
+
- **Services**: `useVoiceBot` hook for state management and API interactions
|
|
165
|
+
- **Components**: Modular UI components following VibeFast design system
|
|
166
|
+
- **Types**: TypeScript interfaces for type safety
|
|
167
|
+
- **Environment**: Secure configuration through environment variables
|
|
168
|
+
- **Lifecycle Management**: Automatic cleanup on component unmount and screen navigation
|
|
169
|
+
|
|
170
|
+
## Security Considerations
|
|
171
|
+
|
|
172
|
+
- Agent ID is configured through environment variables
|
|
173
|
+
- For production apps, consider generating short-lived signed URLs server-side
|
|
174
|
+
- See [ElevenLabs Authentication Docs](https://elevenlabs.io/docs/conversational-ai/customization/authentication) for advanced security
|
|
175
|
+
|
|
176
|
+
## Troubleshooting
|
|
177
|
+
|
|
178
|
+
1. **Build Errors**: Ensure you're using development builds, not Expo Go
|
|
179
|
+
2. **Audio Issues**: Check simulator audio settings and permissions
|
|
180
|
+
3. **Connection Issues**: Verify agent ID and network connectivity
|
|
181
|
+
4. **WebRTC Errors**: Ensure all native dependencies are properly installed
|
|
182
|
+
|
|
183
|
+
## API Reference
|
|
184
|
+
|
|
185
|
+
See the [ElevenLabs React Native SDK Documentation](https://elevenlabs.io/docs/conversational-ai/libraries/react-native) for detailed API reference.
|
package/recipes/voice-bot/apps/native/src/features/voice-bot/components/conversation-status.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ConversationStatus } from '@elevenlabs/react-native';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Text } from '@/components/ui';
|
|
6
|
+
|
|
7
|
+
interface ConversationStatusIndicatorProps {
|
|
8
|
+
status: ConversationStatus;
|
|
9
|
+
conversationId?: string | null;
|
|
10
|
+
isSpeaking?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ConversationStatusIndicator({
|
|
14
|
+
status,
|
|
15
|
+
conversationId,
|
|
16
|
+
isSpeaking,
|
|
17
|
+
}: ConversationStatusIndicatorProps) {
|
|
18
|
+
const getStatusColor = (status: ConversationStatus): string => {
|
|
19
|
+
switch (status) {
|
|
20
|
+
case 'connected':
|
|
21
|
+
return 'bg-success-500';
|
|
22
|
+
case 'connecting':
|
|
23
|
+
return 'bg-warning-500';
|
|
24
|
+
case 'disconnected':
|
|
25
|
+
return 'bg-neutral-400';
|
|
26
|
+
default:
|
|
27
|
+
return 'bg-neutral-400';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getStatusText = (status: ConversationStatus): string => {
|
|
32
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View className="items-center gap-4">
|
|
37
|
+
{/* Status Indicator */}
|
|
38
|
+
<View className="flex-row items-center gap-2">
|
|
39
|
+
<View className={`size-3 rounded-full ${getStatusColor(status)}`} />
|
|
40
|
+
<Text className="text-base font-medium text-foreground">
|
|
41
|
+
{getStatusText(status)}
|
|
42
|
+
</Text>
|
|
43
|
+
</View>
|
|
44
|
+
|
|
45
|
+
{/* Conversation ID Display */}
|
|
46
|
+
{status === 'connected' && conversationId && (
|
|
47
|
+
<View className="w-full rounded-lg border border-border bg-muted p-3">
|
|
48
|
+
<Text className="text-mutedForeground mb-1 text-xs font-semibold">
|
|
49
|
+
Conversation ID:
|
|
50
|
+
</Text>
|
|
51
|
+
<Text className="rounded border border-border bg-background p-2 font-mono text-sm text-foreground">
|
|
52
|
+
{conversationId}
|
|
53
|
+
</Text>
|
|
54
|
+
</View>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{/* Speaking Indicator */}
|
|
58
|
+
{status === 'connected' && (
|
|
59
|
+
<View className="flex-row items-center gap-2">
|
|
60
|
+
<View
|
|
61
|
+
className={`size-3 rounded-full ${
|
|
62
|
+
isSpeaking ? 'bg-success' : 'bg-muted'
|
|
63
|
+
}`}
|
|
64
|
+
/>
|
|
65
|
+
<Text
|
|
66
|
+
className={`text-sm font-medium ${
|
|
67
|
+
isSpeaking ? 'text-success' : 'text-mutedForeground'
|
|
68
|
+
}`}
|
|
69
|
+
>
|
|
70
|
+
{isSpeaking ? '🎤 AI Speaking' : '👂 AI Listening'}
|
|
71
|
+
</Text>
|
|
72
|
+
</View>
|
|
73
|
+
)}
|
|
74
|
+
</View>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Keyboard, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Button, Input, Text } from '@/components/ui';
|
|
5
|
+
|
|
6
|
+
interface MessageInputProps {
|
|
7
|
+
value: string;
|
|
8
|
+
onChangeText: (text: string) => void;
|
|
9
|
+
onSendMessage: () => void;
|
|
10
|
+
onSendContext: () => void;
|
|
11
|
+
onUserActivity: () => void;
|
|
12
|
+
isConnected: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function MessageInput({
|
|
16
|
+
value,
|
|
17
|
+
onChangeText,
|
|
18
|
+
onSendMessage,
|
|
19
|
+
onSendContext,
|
|
20
|
+
onUserActivity,
|
|
21
|
+
isConnected,
|
|
22
|
+
}: MessageInputProps) {
|
|
23
|
+
const handleTextChange = (text: string) => {
|
|
24
|
+
onChangeText(text);
|
|
25
|
+
// Prevent agent from interrupting while user is typing
|
|
26
|
+
if (text.length > 0) {
|
|
27
|
+
onUserActivity();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleSendMessage = () => {
|
|
32
|
+
if (value.trim()) {
|
|
33
|
+
onSendMessage();
|
|
34
|
+
Keyboard.dismiss();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleSendContext = () => {
|
|
39
|
+
if (value.trim()) {
|
|
40
|
+
onSendContext();
|
|
41
|
+
Keyboard.dismiss();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleSubmitEditing = () => {
|
|
46
|
+
handleSendMessage();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (!isConnected) {
|
|
50
|
+
return (
|
|
51
|
+
<View className="rounded-2xl border border-border/50 bg-card/90 p-6 opacity-50 shadow-lg backdrop-blur-md">
|
|
52
|
+
<Text className="text-mutedForeground text-center">
|
|
53
|
+
Connect to start sending messages
|
|
54
|
+
</Text>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View className="rounded-2xl border border-border/50 bg-card/90 p-6 shadow-lg backdrop-blur-md">
|
|
61
|
+
<View className="w-full gap-4">
|
|
62
|
+
<Text className="text-base font-medium text-foreground">
|
|
63
|
+
Send Text Message
|
|
64
|
+
</Text>
|
|
65
|
+
|
|
66
|
+
<Input
|
|
67
|
+
value={value}
|
|
68
|
+
onChangeText={handleTextChange}
|
|
69
|
+
placeholder="Type your message or context..."
|
|
70
|
+
multiline
|
|
71
|
+
numberOfLines={4}
|
|
72
|
+
textAlignVertical="top"
|
|
73
|
+
onSubmitEditing={handleSubmitEditing}
|
|
74
|
+
returnKeyType="send"
|
|
75
|
+
blurOnSubmit={true}
|
|
76
|
+
testID="message-input"
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<View className="gap-3">
|
|
80
|
+
<Button
|
|
81
|
+
label="💬 Send Message"
|
|
82
|
+
onPress={handleSendMessage}
|
|
83
|
+
disabled={!value.trim()}
|
|
84
|
+
variant="default"
|
|
85
|
+
testID="send-message-button"
|
|
86
|
+
/>
|
|
87
|
+
<Button
|
|
88
|
+
label="📝 Send Context"
|
|
89
|
+
onPress={handleSendContext}
|
|
90
|
+
disabled={!value.trim()}
|
|
91
|
+
variant="secondary"
|
|
92
|
+
testID="send-context-button"
|
|
93
|
+
/>
|
|
94
|
+
</View>
|
|
95
|
+
</View>
|
|
96
|
+
</View>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ElevenLabsProvider } from '@elevenlabs/react-native';
|
|
2
|
+
import { BlurView } from 'expo-blur';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Keyboard,
|
|
6
|
+
KeyboardAvoidingView,
|
|
7
|
+
Platform,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableWithoutFeedback,
|
|
10
|
+
View,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import Animated, {
|
|
13
|
+
interpolate,
|
|
14
|
+
useAnimatedRef,
|
|
15
|
+
useAnimatedStyle,
|
|
16
|
+
useScrollOffset,
|
|
17
|
+
} from 'react-native-reanimated';
|
|
18
|
+
import type { AnimatedScrollView } from 'react-native-reanimated/lib/typescript/component/ScrollView';
|
|
19
|
+
|
|
20
|
+
import { Text } from '@/components/ui';
|
|
21
|
+
|
|
22
|
+
import { useVoiceBot } from '../services';
|
|
23
|
+
import type { VoiceBotConfig } from '../types';
|
|
24
|
+
import { ConversationStatusIndicator } from './conversation-status';
|
|
25
|
+
import { MessageInput } from './message-input';
|
|
26
|
+
import { VoiceControls } from './voice-controls';
|
|
27
|
+
|
|
28
|
+
interface VoiceBotScreenProps {
|
|
29
|
+
config: VoiceBotConfig;
|
|
30
|
+
title?: string;
|
|
31
|
+
subtitle?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function VoiceBotContent({ config, title, subtitle }: VoiceBotScreenProps) {
|
|
35
|
+
const { state, actions, textInput, setTextInput } = useVoiceBot(config, {
|
|
36
|
+
onConnect: (conversationId) => {
|
|
37
|
+
console.log('Voice bot connected:', conversationId);
|
|
38
|
+
},
|
|
39
|
+
onDisconnect: (details) => {
|
|
40
|
+
console.log('Voice bot disconnected:', details);
|
|
41
|
+
},
|
|
42
|
+
onError: (message, context) => {
|
|
43
|
+
console.error('Voice bot error:', message, context);
|
|
44
|
+
},
|
|
45
|
+
onMessage: (message) => {
|
|
46
|
+
console.log('Voice bot message:', message);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Animated scroll effects (for blur intensity only)
|
|
51
|
+
const ref = useAnimatedRef<AnimatedScrollView>();
|
|
52
|
+
const scroll = useScrollOffset(ref);
|
|
53
|
+
|
|
54
|
+
const blurStyle = useAnimatedStyle(() => {
|
|
55
|
+
return {
|
|
56
|
+
opacity: interpolate(scroll.value, [0, 200], [0, 1], 'clamp'),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const handleSendMessage = () => {
|
|
61
|
+
actions.sendMessage(textInput);
|
|
62
|
+
setTextInput('');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleSendContext = () => {
|
|
66
|
+
actions.sendContextualUpdate(textInput);
|
|
67
|
+
setTextInput('');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<View style={{ flex: 1 }}>
|
|
72
|
+
{/* Blur Overlay */}
|
|
73
|
+
<Animated.View
|
|
74
|
+
style={[StyleSheet.absoluteFill, { zIndex: -1 }, blurStyle]}
|
|
75
|
+
>
|
|
76
|
+
<BlurView
|
|
77
|
+
style={{ flex: 1 }}
|
|
78
|
+
tint="systemUltraThinMaterial"
|
|
79
|
+
intensity={80}
|
|
80
|
+
/>
|
|
81
|
+
</Animated.View>
|
|
82
|
+
|
|
83
|
+
<Animated.ScrollView
|
|
84
|
+
ref={ref}
|
|
85
|
+
showsVerticalScrollIndicator={false}
|
|
86
|
+
automaticallyAdjustsScrollIndicatorInsets={true}
|
|
87
|
+
contentInsetAdjustmentBehavior="never"
|
|
88
|
+
style={{ backgroundColor: 'transparent' }}
|
|
89
|
+
>
|
|
90
|
+
<KeyboardAvoidingView
|
|
91
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
92
|
+
style={{ backgroundColor: 'transparent' }}
|
|
93
|
+
>
|
|
94
|
+
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
|
|
95
|
+
<View style={{ backgroundColor: 'transparent' }}>
|
|
96
|
+
{/* Spacer so the header area feels roomy */}
|
|
97
|
+
<View
|
|
98
|
+
style={{
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
gap: 8,
|
|
101
|
+
padding: 16,
|
|
102
|
+
flex: 1,
|
|
103
|
+
minHeight: 200,
|
|
104
|
+
backgroundColor: 'transparent',
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
{/* Voice Bot Content */}
|
|
109
|
+
<View style={{ paddingHorizontal: 24, gap: 24 }}>
|
|
110
|
+
{/* Header */}
|
|
111
|
+
<View className="items-center gap-4 rounded-3xl border border-border/50 bg-card/50 p-6 shadow-lg backdrop-blur-md">
|
|
112
|
+
<View className="mb-2 rounded-full bg-primary/20 p-4">
|
|
113
|
+
<Text className="text-4xl">🗣️</Text>
|
|
114
|
+
</View>
|
|
115
|
+
<Text className="text-center text-3xl font-bold text-foreground">
|
|
116
|
+
{title || 'Voice Assistant'}
|
|
117
|
+
</Text>
|
|
118
|
+
{subtitle && (
|
|
119
|
+
<Text className="text-mutedForeground text-center text-lg leading-6">
|
|
120
|
+
{subtitle}
|
|
121
|
+
</Text>
|
|
122
|
+
)}
|
|
123
|
+
</View>
|
|
124
|
+
|
|
125
|
+
{/* Status Indicator */}
|
|
126
|
+
<View className="rounded-2xl border border-border/50 bg-card/90 p-6 shadow-lg backdrop-blur-md">
|
|
127
|
+
<ConversationStatusIndicator
|
|
128
|
+
status={state.status}
|
|
129
|
+
conversationId={state.conversationId}
|
|
130
|
+
isSpeaking={state.isSpeaking}
|
|
131
|
+
/>
|
|
132
|
+
</View>
|
|
133
|
+
|
|
134
|
+
{/* Voice Controls */}
|
|
135
|
+
<View className="rounded-2xl border border-border/50 bg-card/90 p-6 shadow-lg backdrop-blur-md">
|
|
136
|
+
<VoiceControls
|
|
137
|
+
status={state.status}
|
|
138
|
+
isStarting={state.isStarting}
|
|
139
|
+
canSendFeedback={state.canSendFeedback}
|
|
140
|
+
onStartConversation={actions.startConversation}
|
|
141
|
+
onEndConversation={actions.endConversation}
|
|
142
|
+
onSendFeedback={actions.sendFeedback}
|
|
143
|
+
/>
|
|
144
|
+
</View>
|
|
145
|
+
|
|
146
|
+
{/* Message Input */}
|
|
147
|
+
<MessageInput
|
|
148
|
+
value={textInput}
|
|
149
|
+
onChangeText={setTextInput}
|
|
150
|
+
onSendMessage={handleSendMessage}
|
|
151
|
+
onSendContext={handleSendContext}
|
|
152
|
+
onUserActivity={actions.sendUserActivity}
|
|
153
|
+
isConnected={state.status === 'connected'}
|
|
154
|
+
/>
|
|
155
|
+
|
|
156
|
+
{/* Bottom spacing */}
|
|
157
|
+
<View style={{ height: 80 }} />
|
|
158
|
+
</View>
|
|
159
|
+
</View>
|
|
160
|
+
</TouchableWithoutFeedback>
|
|
161
|
+
</KeyboardAvoidingView>
|
|
162
|
+
</Animated.ScrollView>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function VoiceBotScreen(props: VoiceBotScreenProps) {
|
|
168
|
+
return (
|
|
169
|
+
<ElevenLabsProvider>
|
|
170
|
+
<VoiceBotContent {...props} />
|
|
171
|
+
</ElevenLabsProvider>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ConversationStatus } from '@elevenlabs/react-native';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Button, Text } from '@/components/ui';
|
|
6
|
+
|
|
7
|
+
interface VoiceControlsProps {
|
|
8
|
+
status: ConversationStatus;
|
|
9
|
+
isStarting: boolean;
|
|
10
|
+
canSendFeedback: boolean;
|
|
11
|
+
onStartConversation: () => void;
|
|
12
|
+
onEndConversation: () => void;
|
|
13
|
+
onSendFeedback: (isPositive: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function VoiceControls({
|
|
17
|
+
status,
|
|
18
|
+
isStarting,
|
|
19
|
+
canSendFeedback,
|
|
20
|
+
onStartConversation,
|
|
21
|
+
onEndConversation,
|
|
22
|
+
onSendFeedback,
|
|
23
|
+
}: VoiceControlsProps) {
|
|
24
|
+
const canStart = status === 'disconnected' && !isStarting;
|
|
25
|
+
const canEnd = status === 'connected';
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View className="w-full gap-2">
|
|
29
|
+
{/* Main Controls - Vertical stack for long button text */}
|
|
30
|
+
<View className="gap-2">
|
|
31
|
+
<Button
|
|
32
|
+
label={isStarting ? 'Starting...' : 'Start Conversation'}
|
|
33
|
+
onPress={onStartConversation}
|
|
34
|
+
disabled={!canStart}
|
|
35
|
+
variant={canStart ? 'default' : 'outline'}
|
|
36
|
+
testID="start-conversation-button"
|
|
37
|
+
/>
|
|
38
|
+
<Button
|
|
39
|
+
label="End Conversation"
|
|
40
|
+
onPress={onEndConversation}
|
|
41
|
+
disabled={!canEnd}
|
|
42
|
+
variant={canEnd ? 'destructive' : 'outline'}
|
|
43
|
+
testID="end-conversation-button"
|
|
44
|
+
/>
|
|
45
|
+
</View>
|
|
46
|
+
|
|
47
|
+
{/* Feedback Controls */}
|
|
48
|
+
{status === 'connected' && canSendFeedback && (
|
|
49
|
+
<View className="mt-4 items-center gap-3">
|
|
50
|
+
<Text className="text-base font-medium text-foreground">
|
|
51
|
+
How was that response?
|
|
52
|
+
</Text>
|
|
53
|
+
<View className="flex-row gap-4">
|
|
54
|
+
<Button
|
|
55
|
+
label="👍 Like"
|
|
56
|
+
onPress={() => onSendFeedback(true)}
|
|
57
|
+
variant="outline"
|
|
58
|
+
size="sm"
|
|
59
|
+
testID="feedback-like-button"
|
|
60
|
+
/>
|
|
61
|
+
<Button
|
|
62
|
+
label="👎 Dislike"
|
|
63
|
+
onPress={() => onSendFeedback(false)}
|
|
64
|
+
variant="outline"
|
|
65
|
+
size="sm"
|
|
66
|
+
testID="feedback-dislike-button"
|
|
67
|
+
/>
|
|
68
|
+
</View>
|
|
69
|
+
</View>
|
|
70
|
+
)}
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-voice-bot';
|