mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 16:15:25 +02:00
feat: add voice-to-text input using Web Speech API
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { ExcelRow, COLUMNS } from '../types';
|
||||
import { X, Sparkles, Save, Loader2, Languages, Package, CheckCircle2 } from 'lucide-react';
|
||||
import { X, Sparkles, Save, Loader2, Languages, Package, CheckCircle2, Mic, MicOff } from 'lucide-react';
|
||||
import { generateGemini } from '../services/gemini';
|
||||
import { cn } from '../lib/utils';
|
||||
import { useSpeechRecognition } from '../lib/useSpeechRecognition';
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
|
||||
interface EditPanelProps {
|
||||
@@ -49,6 +50,9 @@ interface FieldEditorProps {
|
||||
onGenerate: () => void;
|
||||
onChange: (val: string) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent) => void;
|
||||
isListening?: boolean;
|
||||
onToggleVoice?: () => void;
|
||||
voiceSupported?: boolean;
|
||||
}
|
||||
|
||||
const FieldEditor = ({
|
||||
@@ -61,7 +65,10 @@ const FieldEditor = ({
|
||||
isLoading,
|
||||
onGenerate,
|
||||
onChange,
|
||||
onKeyDown
|
||||
onKeyDown,
|
||||
isListening,
|
||||
onToggleVoice,
|
||||
voiceSupported
|
||||
}: FieldEditorProps) => (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -73,6 +80,21 @@ const FieldEditor = ({
|
||||
Can translate from existing
|
||||
</div>
|
||||
)}
|
||||
{voiceSupported && (
|
||||
<button
|
||||
onClick={onToggleVoice}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 text-xs font-medium px-2 py-1 rounded transition-colors",
|
||||
isListening
|
||||
? "bg-red-600/20 text-red-400 animate-pulse"
|
||||
: "bg-slate-700/50 text-slate-400 hover:bg-slate-600 hover:text-white"
|
||||
)}
|
||||
title={isListening ? "Stop recording" : "Voice input"}
|
||||
>
|
||||
{isListening ? <MicOff className="w-3 h-3" /> : <Mic className="w-3 h-3" />}
|
||||
{isListening ? 'Stop' : 'Voice'}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onGenerate}
|
||||
disabled={isLoading}
|
||||
@@ -127,6 +149,17 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
const [pendingGeminiField, setPendingGeminiField] = useState<keyof typeof formData | null>(null);
|
||||
const [generatedFields, setGeneratedFields] = useState<Set<string>>(new Set());
|
||||
|
||||
const handleSpeechResult = useCallback((transcript: string) => {
|
||||
setFormData(prev => ({ ...prev, longDe: prev.longDe + ' ' + transcript }));
|
||||
}, []);
|
||||
|
||||
const { isListening: isListeningDe, start: startListeningDe, stop: stopListeningDe, isSupported: speechSupported } = useSpeechRecognition({ onResult: handleSpeechResult });
|
||||
|
||||
const handleSpeechResultEn = useCallback((transcript: string) => {
|
||||
setFormData(prev => ({ ...prev, longEn: prev.longEn + ' ' + transcript }));
|
||||
}, []);
|
||||
const { isListening: isListeningEn, start: startListeningEn, stop: stopListeningEn } = useSpeechRecognition({ onResult: handleSpeechResultEn, lang: 'en-US' });
|
||||
|
||||
const isModified = (field: keyof typeof formData) => {
|
||||
const colMap: Record<string, number> = {
|
||||
longDe: COLUMNS.LONG_DE,
|
||||
@@ -351,6 +384,9 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
onGenerate={() => handleGenerate('longDe')}
|
||||
onChange={(val) => setFormData(p => ({...p, longDe: val}))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
isListening={isListeningDe}
|
||||
onToggleVoice={isListeningDe ? stopListeningDe : startListeningDe}
|
||||
voiceSupported={speechSupported}
|
||||
/>
|
||||
<FieldEditor
|
||||
title="Long Description (EN)"
|
||||
@@ -363,6 +399,9 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
onGenerate={() => handleGenerate('longEn')}
|
||||
onChange={(val) => setFormData(p => ({...p, longEn: val}))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
isListening={isListeningEn}
|
||||
onToggleVoice={isListeningEn ? stopListeningEn : startListeningEn}
|
||||
voiceSupported={speechSupported}
|
||||
/>
|
||||
<FieldEditor
|
||||
title="Short Description (DE)"
|
||||
|
||||
Reference in New Issue
Block a user