react-native-voice-ts 1.0.0 → 1.0.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.
Files changed (41) hide show
  1. package/README.md +914 -83
  2. package/dist/components/MicIcon.d.ts +23 -0
  3. package/dist/components/MicIcon.d.ts.map +1 -0
  4. package/dist/components/MicIcon.js +28 -0
  5. package/dist/components/MicIcon.js.map +1 -0
  6. package/dist/components/VoiceMicrophone.d.ts +93 -0
  7. package/dist/components/VoiceMicrophone.d.ts.map +1 -0
  8. package/dist/components/VoiceMicrophone.js +239 -0
  9. package/dist/components/VoiceMicrophone.js.map +1 -0
  10. package/dist/components/index.d.ts +5 -0
  11. package/dist/components/index.d.ts.map +1 -0
  12. package/dist/components/index.js +3 -0
  13. package/dist/components/index.js.map +1 -0
  14. package/dist/hooks/index.d.ts +3 -0
  15. package/dist/hooks/index.d.ts.map +1 -0
  16. package/dist/hooks/index.js +2 -0
  17. package/dist/hooks/index.js.map +1 -0
  18. package/dist/hooks/useVoiceRecognition.d.ts +89 -0
  19. package/dist/hooks/useVoiceRecognition.d.ts.map +1 -0
  20. package/dist/hooks/useVoiceRecognition.js +227 -0
  21. package/dist/hooks/useVoiceRecognition.js.map +1 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -0
  25. package/dist/index.js.map +1 -1
  26. package/ios/Voice.xcodeproj/project.xcworkspace/xcuserdata/olumayowadaniel.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  27. package/ios/Voice.xcodeproj/project.xcworkspace/xcuserdata/rudie_shahinian.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  28. package/package.json +25 -7
  29. package/plugin/src/withVoice.ts +74 -0
  30. package/plugin/tsconfig.json +10 -0
  31. package/src/components/MicIcon.tsx +72 -0
  32. package/src/components/VoiceMicrophone.tsx +345 -0
  33. package/src/components/index.ts +4 -0
  34. package/src/hooks/index.ts +5 -0
  35. package/src/hooks/useVoiceRecognition.ts +333 -0
  36. package/src/images/mic.svg +16 -0
  37. package/src/index.ts +15 -0
  38. package/.nvmrc +0 -1
  39. package/.prettierrc +0 -5
  40. package/.releaserc +0 -15
  41. package/MIGRATION_SUMMARY.md +0 -510
@@ -0,0 +1,345 @@
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+ import Voice from '../index';
3
+ import type { SpeechErrorEvent, SpeechResultsEvent } from '../VoiceModuleTypes';
4
+
5
+ export interface VoiceMicrophoneProps {
6
+ /**
7
+ * Callback fired when speech is recognized and converted to text
8
+ */
9
+ onSpeechResult?: (text: string) => void;
10
+
11
+ /**
12
+ * Callback fired when partial results are available (real-time)
13
+ */
14
+ onPartialResult?: (text: string) => void;
15
+
16
+ /**
17
+ * Callback fired when recording starts
18
+ */
19
+ onStart?: () => void;
20
+
21
+ /**
22
+ * Callback fired when recording stops
23
+ */
24
+ onStop?: () => void;
25
+
26
+ /**
27
+ * Callback fired when an error occurs
28
+ */
29
+ onError?: (error: string) => void;
30
+
31
+ /**
32
+ * Language locale for speech recognition
33
+ * @default 'en-US'
34
+ */
35
+ locale?: string;
36
+
37
+ /**
38
+ * Whether to automatically start recording on mount
39
+ * @default false
40
+ */
41
+ autoStart?: boolean;
42
+
43
+ /**
44
+ * Whether to enable partial results (real-time transcription)
45
+ * @default true
46
+ */
47
+ enablePartialResults?: boolean;
48
+
49
+ /**
50
+ * Whether to continue listening after getting results (continuous mode)
51
+ * When enabled, the microphone will automatically restart after getting results
52
+ * @default false
53
+ */
54
+ continuous?: boolean;
55
+
56
+ /**
57
+ * Maximum silence duration in milliseconds before stopping (continuous mode)
58
+ * Only applies when continuous mode is enabled
59
+ * @default 5000 (5 seconds)
60
+ */
61
+ maxSilenceDuration?: number;
62
+
63
+ /**
64
+ * Custom render function for the component
65
+ * Receives isRecording state and control functions
66
+ */
67
+ children?: (props: {
68
+ isRecording: boolean;
69
+ recognizedText: string;
70
+ partialText: string;
71
+ start: () => Promise<void>;
72
+ stop: () => Promise<void>;
73
+ cancel: () => Promise<void>;
74
+ error: string | null;
75
+ }) => React.ReactNode;
76
+ }
77
+
78
+ /**
79
+ * VoiceMicrophone Component
80
+ *
81
+ * A ready-to-use voice recognition component that handles microphone access,
82
+ * speech recognition, and provides real-time text results.
83
+ *
84
+ * @example
85
+ * ```tsx
86
+ * // Simple usage with callback
87
+ * <VoiceMicrophone
88
+ * onSpeechResult={(text) => setSearchQuery(text)}
89
+ * />
90
+ *
91
+ * // Custom render with full control
92
+ * <VoiceMicrophone locale="en-US">
93
+ * {({ isRecording, recognizedText, start, stop }) => (
94
+ * <View>
95
+ * <Text>{recognizedText}</Text>
96
+ * <Button
97
+ * onPress={isRecording ? stop : start}
98
+ * title={isRecording ? 'Stop' : 'Start'}
99
+ * />
100
+ * </View>
101
+ * )}
102
+ * </VoiceMicrophone>
103
+ * ```
104
+ */
105
+ const VoiceMicrophone: React.FC<VoiceMicrophoneProps> = ({
106
+ onSpeechResult,
107
+ onPartialResult,
108
+ onStart,
109
+ onStop,
110
+ onError,
111
+ locale = 'en-US',
112
+ autoStart = false,
113
+ enablePartialResults = true,
114
+ continuous = false,
115
+ maxSilenceDuration = 5000,
116
+ children,
117
+ }) => {
118
+ const [isRecording, setIsRecording] = useState(false);
119
+ const [recognizedText, setRecognizedText] = useState('');
120
+ const [partialText, setPartialText] = useState('');
121
+ const [error, setError] = useState<string | null>(null);
122
+ const [shouldContinue, setShouldContinue] = useState(false);
123
+ const silenceTimerRef = React.useRef<NodeJS.Timeout | null>(null);
124
+
125
+ useEffect(() => {
126
+ // Clear any existing timers on cleanup
127
+ return () => {
128
+ if (silenceTimerRef.current) {
129
+ clearTimeout(silenceTimerRef.current);
130
+ }
131
+ };
132
+ }, []);
133
+
134
+ useEffect(() => {
135
+ // Set up event listeners
136
+ Voice.onSpeechStart = () => {
137
+ setIsRecording(true);
138
+ setError(null);
139
+ if (silenceTimerRef.current) {
140
+ clearTimeout(silenceTimerRef.current);
141
+ }
142
+ onStart?.();
143
+ };
144
+
145
+ Voice.onSpeechEnd = async () => {
146
+ setIsRecording(false);
147
+
148
+ // In continuous mode, restart listening after results
149
+ if (continuous && shouldContinue) {
150
+ // Small delay before restarting
151
+ setTimeout(async () => {
152
+ if (shouldContinue) {
153
+ try {
154
+ await Voice.start(locale, {
155
+ EXTRA_PARTIAL_RESULTS: enablePartialResults,
156
+ });
157
+ } catch (err) {
158
+ console.error('Failed to restart voice recognition:', err);
159
+ }
160
+ }
161
+ }, 100);
162
+ } else {
163
+ onStop?.();
164
+ }
165
+ };
166
+
167
+ Voice.onSpeechError = (e: SpeechErrorEvent) => {
168
+ const errorMessage = e.error?.message || 'Unknown error';
169
+ setError(errorMessage);
170
+ setIsRecording(false);
171
+ setShouldContinue(false);
172
+ if (silenceTimerRef.current) {
173
+ clearTimeout(silenceTimerRef.current);
174
+ }
175
+ onError?.(errorMessage);
176
+ };
177
+
178
+ Voice.onSpeechResults = (e: SpeechResultsEvent) => {
179
+ if (e.value && e.value.length > 0) {
180
+ const text = e.value[0];
181
+
182
+ // In continuous mode, append new text to existing
183
+ if (continuous && recognizedText) {
184
+ const updatedText = recognizedText + ' ' + text;
185
+ setRecognizedText(updatedText);
186
+ onSpeechResult?.(updatedText);
187
+ } else {
188
+ setRecognizedText(text);
189
+ onSpeechResult?.(text);
190
+ }
191
+
192
+ setPartialText('');
193
+ }
194
+ };
195
+
196
+ if (enablePartialResults) {
197
+ Voice.onSpeechPartialResults = (e: SpeechResultsEvent) => {
198
+ if (e.value && e.value.length > 0) {
199
+ const text = e.value[0];
200
+ setPartialText(text);
201
+ onPartialResult?.(text);
202
+
203
+ // Reset silence timer on partial results (user is speaking)
204
+ if (continuous && silenceTimerRef.current) {
205
+ clearTimeout(silenceTimerRef.current);
206
+ silenceTimerRef.current = setTimeout(() => {
207
+ if (shouldContinue) {
208
+ stop();
209
+ }
210
+ }, maxSilenceDuration);
211
+ }
212
+ }
213
+ };
214
+ }
215
+
216
+ // Cleanup
217
+ return () => {
218
+ Voice.destroy().then(Voice.removeAllListeners);
219
+ };
220
+ }, [
221
+ onSpeechResult,
222
+ onPartialResult,
223
+ onStart,
224
+ onStop,
225
+ onError,
226
+ enablePartialResults,
227
+ continuous,
228
+ shouldContinue,
229
+ recognizedText,
230
+ locale,
231
+ maxSilenceDuration,
232
+ ]);
233
+
234
+ // Auto-start if enabled
235
+ useEffect(() => {
236
+ if (autoStart) {
237
+ start();
238
+ }
239
+ // eslint-disable-next-line react-hooks/exhaustive-deps
240
+ }, [autoStart]);
241
+
242
+ const start = useCallback(async () => {
243
+ try {
244
+ setError(null);
245
+ if (!continuous) {
246
+ setRecognizedText('');
247
+ setPartialText('');
248
+ }
249
+ setShouldContinue(true);
250
+
251
+ // Check permission (Android only)
252
+ const hasPermission = await Voice.checkMicrophonePermission();
253
+ if (!hasPermission) {
254
+ const granted = await Voice.requestMicrophonePermission();
255
+ if (!granted) {
256
+ setError('Microphone permission denied');
257
+ return;
258
+ }
259
+ }
260
+
261
+ await Voice.start(locale, {
262
+ EXTRA_PARTIAL_RESULTS: enablePartialResults,
263
+ });
264
+
265
+ // Start silence timer if in continuous mode
266
+ if (continuous) {
267
+ silenceTimerRef.current = setTimeout(() => {
268
+ if (shouldContinue) {
269
+ stop();
270
+ }
271
+ }, maxSilenceDuration);
272
+ }
273
+ } catch (e) {
274
+ const errorMessage =
275
+ e instanceof Error ? e.message : 'Failed to start recording';
276
+ setError(errorMessage);
277
+ setShouldContinue(false);
278
+ onError?.(errorMessage);
279
+ }
280
+ }, [
281
+ locale,
282
+ enablePartialResults,
283
+ onError,
284
+ continuous,
285
+ maxSilenceDuration,
286
+ shouldContinue,
287
+ ]);
288
+
289
+ const stop = useCallback(async () => {
290
+ try {
291
+ setShouldContinue(false);
292
+ if (silenceTimerRef.current) {
293
+ clearTimeout(silenceTimerRef.current);
294
+ silenceTimerRef.current = null;
295
+ }
296
+ await Voice.stop();
297
+ onStop?.();
298
+ } catch (e) {
299
+ const errorMessage =
300
+ e instanceof Error ? e.message : 'Failed to stop recording';
301
+ setError(errorMessage);
302
+ onError?.(errorMessage);
303
+ }
304
+ }, [onError, onStop]);
305
+
306
+ const cancel = useCallback(async () => {
307
+ try {
308
+ setShouldContinue(false);
309
+ if (silenceTimerRef.current) {
310
+ clearTimeout(silenceTimerRef.current);
311
+ silenceTimerRef.current = null;
312
+ }
313
+ await Voice.cancel();
314
+ setRecognizedText('');
315
+ setPartialText('');
316
+ } catch (e) {
317
+ const errorMessage =
318
+ e instanceof Error ? e.message : 'Failed to cancel recording';
319
+ setError(errorMessage);
320
+ onError?.(errorMessage);
321
+ }
322
+ }, [onError]);
323
+
324
+ // If children render prop is provided, use it
325
+ if (children) {
326
+ return (
327
+ <>
328
+ {children({
329
+ isRecording,
330
+ recognizedText,
331
+ partialText,
332
+ start,
333
+ stop,
334
+ cancel,
335
+ error,
336
+ })}
337
+ </>
338
+ );
339
+ }
340
+
341
+ // Default: render nothing (headless component)
342
+ return null;
343
+ };
344
+
345
+ export default VoiceMicrophone;
@@ -0,0 +1,4 @@
1
+ export { default as VoiceMicrophone } from './VoiceMicrophone';
2
+ export type { VoiceMicrophoneProps } from './VoiceMicrophone';
3
+ export { MicIcon, MicOffIcon } from './MicIcon';
4
+ export type { MicIconProps, MicOffIconProps } from './MicIcon';
@@ -0,0 +1,5 @@
1
+ export { useVoiceRecognition } from './useVoiceRecognition';
2
+ export type {
3
+ UseVoiceRecognitionOptions,
4
+ UseVoiceRecognitionReturn,
5
+ } from './useVoiceRecognition';
@@ -0,0 +1,333 @@
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+ import Voice from '../index';
3
+ import type { SpeechErrorEvent, SpeechResultsEvent } from '../VoiceModuleTypes';
4
+
5
+ export interface UseVoiceRecognitionOptions {
6
+ /**
7
+ * Language locale for speech recognition
8
+ * @default 'en-US'
9
+ */
10
+ locale?: string;
11
+
12
+ /**
13
+ * Whether to enable partial results (real-time transcription)
14
+ * @default true
15
+ */
16
+ enablePartialResults?: boolean;
17
+
18
+ /**
19
+ * Whether to continue listening after getting results (continuous mode)
20
+ * When enabled, the microphone will automatically restart after getting results
21
+ * @default false
22
+ */
23
+ continuous?: boolean;
24
+
25
+ /**
26
+ * Maximum silence duration in milliseconds before stopping (continuous mode)
27
+ * Only applies when continuous mode is enabled
28
+ * @default 5000 (5 seconds)
29
+ */
30
+ maxSilenceDuration?: number;
31
+
32
+ /**
33
+ * Callback fired when speech is recognized
34
+ */
35
+ onResult?: (text: string) => void;
36
+
37
+ /**
38
+ * Callback fired when an error occurs
39
+ */
40
+ onError?: (error: string) => void;
41
+ }
42
+
43
+ export interface UseVoiceRecognitionReturn {
44
+ /**
45
+ * Whether voice recognition is currently active
46
+ */
47
+ isRecording: boolean;
48
+
49
+ /**
50
+ * Final recognized text results
51
+ */
52
+ results: string[];
53
+
54
+ /**
55
+ * Partial results (real-time transcription)
56
+ */
57
+ partialResults: string[];
58
+
59
+ /**
60
+ * Error message if an error occurred
61
+ */
62
+ error: string | null;
63
+
64
+ /**
65
+ * Start voice recognition
66
+ */
67
+ start: () => Promise<void>;
68
+
69
+ /**
70
+ * Stop voice recognition and get final results
71
+ */
72
+ stop: () => Promise<void>;
73
+
74
+ /**
75
+ * Cancel voice recognition without getting results
76
+ */
77
+ cancel: () => Promise<void>;
78
+
79
+ /**
80
+ * Reset all state
81
+ */
82
+ reset: () => void;
83
+ }
84
+
85
+ /**
86
+ * Custom hook for voice recognition
87
+ *
88
+ * Provides a simple interface for speech-to-text functionality with automatic
89
+ * event listener setup and cleanup.
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * const { isRecording, results, start, stop } = useVoiceRecognition({
94
+ * locale: 'en-US',
95
+ * onResult: (text) => setSearchQuery(text),
96
+ * });
97
+ *
98
+ * // In your component
99
+ * <Button
100
+ * onPress={isRecording ? stop : start}
101
+ * title={isRecording ? 'Stop' : 'Start Recording'}
102
+ * />
103
+ * <Text>{results[0]}</Text>
104
+ * ```
105
+ */
106
+ export const useVoiceRecognition = (
107
+ options: UseVoiceRecognitionOptions = {},
108
+ ): UseVoiceRecognitionReturn => {
109
+ const {
110
+ locale = 'en-US',
111
+ enablePartialResults = true,
112
+ continuous = false,
113
+ maxSilenceDuration = 5000,
114
+ onResult,
115
+ onError,
116
+ } = options;
117
+
118
+ const [isRecording, setIsRecording] = useState(false);
119
+ const [results, setResults] = useState<string[]>([]);
120
+ const [partialResults, setPartialResults] = useState<string[]>([]);
121
+ const [error, setError] = useState<string | null>(null);
122
+ const [shouldContinue, setShouldContinue] = useState(false);
123
+ const silenceTimerRef = React.useRef<NodeJS.Timeout | null>(null);
124
+ const accumulatedTextRef = React.useRef<string>('');
125
+
126
+ useEffect(() => {
127
+ // Clear any existing timers on cleanup
128
+ return () => {
129
+ if (silenceTimerRef.current) {
130
+ clearTimeout(silenceTimerRef.current);
131
+ }
132
+ };
133
+ }, []);
134
+
135
+ useEffect(() => {
136
+ // Set up event listeners
137
+ Voice.onSpeechStart = () => {
138
+ setIsRecording(true);
139
+ setError(null);
140
+ if (silenceTimerRef.current) {
141
+ clearTimeout(silenceTimerRef.current);
142
+ }
143
+ };
144
+
145
+ Voice.onSpeechEnd = async () => {
146
+ setIsRecording(false);
147
+
148
+ // In continuous mode, restart listening after results
149
+ if (continuous && shouldContinue) {
150
+ setTimeout(async () => {
151
+ if (shouldContinue) {
152
+ try {
153
+ await Voice.start(locale, {
154
+ EXTRA_PARTIAL_RESULTS: enablePartialResults,
155
+ });
156
+ } catch (err) {
157
+ console.error('Failed to restart voice recognition:', err);
158
+ }
159
+ }
160
+ }, 100);
161
+ }
162
+ };
163
+
164
+ Voice.onSpeechError = (e: SpeechErrorEvent) => {
165
+ const errorMessage = e.error?.message || 'Unknown error';
166
+ setError(errorMessage);
167
+ setIsRecording(false);
168
+ setShouldContinue(false);
169
+ if (silenceTimerRef.current) {
170
+ clearTimeout(silenceTimerRef.current);
171
+ }
172
+ onError?.(errorMessage);
173
+ };
174
+
175
+ Voice.onSpeechResults = (e: SpeechResultsEvent) => {
176
+ if (e.value && e.value.length > 0) {
177
+ if (continuous) {
178
+ // Append new text to accumulated text
179
+ const newText = e.value[0];
180
+ accumulatedTextRef.current = accumulatedTextRef.current
181
+ ? accumulatedTextRef.current + ' ' + newText
182
+ : newText;
183
+ setResults([accumulatedTextRef.current, ...e.value.slice(1)]);
184
+ onResult?.(accumulatedTextRef.current);
185
+ } else {
186
+ setResults(e.value);
187
+ const firstResult = e.value[0];
188
+ if (firstResult) {
189
+ onResult?.(firstResult);
190
+ }
191
+ }
192
+ }
193
+ };
194
+
195
+ if (enablePartialResults) {
196
+ Voice.onSpeechPartialResults = (e: SpeechResultsEvent) => {
197
+ if (e.value && e.value.length > 0) {
198
+ setPartialResults(e.value);
199
+
200
+ // Reset silence timer on partial results (user is speaking)
201
+ if (continuous && silenceTimerRef.current) {
202
+ clearTimeout(silenceTimerRef.current);
203
+ silenceTimerRef.current = setTimeout(() => {
204
+ if (shouldContinue) {
205
+ stop();
206
+ }
207
+ }, maxSilenceDuration);
208
+ }
209
+ }
210
+ };
211
+ }
212
+
213
+ // Cleanup
214
+ return () => {
215
+ Voice.destroy().then(Voice.removeAllListeners);
216
+ };
217
+ }, [
218
+ enablePartialResults,
219
+ onResult,
220
+ onError,
221
+ continuous,
222
+ shouldContinue,
223
+ locale,
224
+ maxSilenceDuration,
225
+ ]);
226
+
227
+ const start = useCallback(async () => {
228
+ try {
229
+ setError(null);
230
+ if (!continuous) {
231
+ setResults([]);
232
+ setPartialResults([]);
233
+ accumulatedTextRef.current = '';
234
+ }
235
+ setShouldContinue(true);
236
+
237
+ // Check permission (Android only)
238
+ const hasPermission = await Voice.checkMicrophonePermission();
239
+ if (!hasPermission) {
240
+ const granted = await Voice.requestMicrophonePermission();
241
+ if (!granted) {
242
+ setError('Microphone permission denied');
243
+ return;
244
+ }
245
+ }
246
+
247
+ await Voice.start(locale, {
248
+ EXTRA_PARTIAL_RESULTS: enablePartialResults,
249
+ });
250
+
251
+ // Start silence timer if in continuous mode
252
+ if (continuous) {
253
+ silenceTimerRef.current = setTimeout(() => {
254
+ if (shouldContinue) {
255
+ stop();
256
+ }
257
+ }, maxSilenceDuration);
258
+ }
259
+ } catch (e) {
260
+ const errorMessage =
261
+ e instanceof Error ? e.message : 'Failed to start recording';
262
+ setError(errorMessage);
263
+ setShouldContinue(false);
264
+ onError?.(errorMessage);
265
+ }
266
+ }, [
267
+ locale,
268
+ enablePartialResults,
269
+ onError,
270
+ continuous,
271
+ maxSilenceDuration,
272
+ shouldContinue,
273
+ ]);
274
+
275
+ const stop = useCallback(async () => {
276
+ try {
277
+ setShouldContinue(false);
278
+ if (silenceTimerRef.current) {
279
+ clearTimeout(silenceTimerRef.current);
280
+ silenceTimerRef.current = null;
281
+ }
282
+ await Voice.stop();
283
+ } catch (e) {
284
+ const errorMessage =
285
+ e instanceof Error ? e.message : 'Failed to stop recording';
286
+ setError(errorMessage);
287
+ onError?.(errorMessage);
288
+ }
289
+ }, [onError]);
290
+
291
+ const cancel = useCallback(async () => {
292
+ try {
293
+ setShouldContinue(false);
294
+ if (silenceTimerRef.current) {
295
+ clearTimeout(silenceTimerRef.current);
296
+ silenceTimerRef.current = null;
297
+ }
298
+ await Voice.cancel();
299
+ setResults([]);
300
+ setPartialResults([]);
301
+ accumulatedTextRef.current = '';
302
+ } catch (e) {
303
+ const errorMessage =
304
+ e instanceof Error ? e.message : 'Failed to cancel recording';
305
+ setError(errorMessage);
306
+ onError?.(errorMessage);
307
+ }
308
+ }, [onError]);
309
+
310
+ const reset = useCallback(() => {
311
+ setResults([]);
312
+ setPartialResults([]);
313
+ setError(null);
314
+ setIsRecording(false);
315
+ accumulatedTextRef.current = '';
316
+ setShouldContinue(false);
317
+ if (silenceTimerRef.current) {
318
+ clearTimeout(silenceTimerRef.current);
319
+ silenceTimerRef.current = null;
320
+ }
321
+ }, []);
322
+
323
+ return {
324
+ isRecording,
325
+ results,
326
+ partialResults,
327
+ error,
328
+ start,
329
+ stop,
330
+ cancel,
331
+ reset,
332
+ };
333
+ };
@@ -0,0 +1,16 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="24"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ stroke-width="2"
9
+ stroke-linecap="round"
10
+ stroke-linejoin="round"
11
+ class="lucide lucide-mic-icon lucide-mic"
12
+ >
13
+ <path d="M12 19v3" />
14
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
15
+ <rect x="9" y="2" width="6" height="13" rx="3" />
16
+ </svg>;