import { useState, useRef } from 'react' import { View, Text, Pressable, StyleSheet, ScrollView, ActivityIndicator, Alert, } from 'react-native' import { COLORS, API_BASE } from '../../constants/Config' import { getToken } from '../../utils/auth' import { VoiceInput } from '../../components/VoiceInput' // 음성 명령 → ITSM 매핑 const VOICE_SHORTCUTS = [ { label: '서버 상태', command: '/server status', icon: '🖥️' }, { label: 'SR 만들기', command: '/sr create', icon: '📋' }, { label: '배포 시작', command: '/deploy', icon: '🚀' }, { label: '장애 보고', command: '/incident create',icon: '🚨' }, { label: '대시보드', command: '/dashboard', icon: '📊' }, { label: '경보 목록', command: '/alert list', icon: '🔔' }, ] export default function VoiceTab() { const [isListening, setListening] = useState(false) const [transcript, setTranscript] = useState('') const [result, setResult] = useState<{ cmd: string; response: string } | null>(null) const [loading, setLoading] = useState(false) const [history, setHistory] = useState<{ text: string; cmd: string; time: string }[]>([]) async function processVoice(text: string) { if (!text.trim()) return setTranscript(text) setLoading(true) try { const token = await getToken() const res = await fetch(`${API_BASE}/api/ux/voice-process`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ text, context: 'voice-tab' }), }) const data = await res.json() if (data.mapped_command) { setResult({ cmd: data.mapped_command, response: `실행: ${data.mapped_command}` }) setHistory(prev => [{ text, cmd: data.mapped_command, time: new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' }), }, ...prev.slice(0, 9)]) } else { setResult({ cmd: '', response: '명령을 인식하지 못했습니다. 다시 시도하거나 아래 빠른 명령을 사용하세요.' }) } } catch (e) { setResult({ cmd: '', response: '서버 연결 오류' }) } finally { setLoading(false) } } async function runShortcut(command: string, label: string) { setTranscript(label) setLoading(true) setResult({ cmd: command, response: `실행 중: ${command}` }) setHistory(prev => [{ text: label, cmd: command, time: new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' }), }, ...prev.slice(0, 9)]) setLoading(false) } return ( {/* 헤더 */} 🎤 음성 명령 한국어로 말하거나 아래 빠른 명령을 탭하세요 {/* 메인 마이크 버튼 */} {isListening ? '듣고 있어요... 명령을 말씀하세요' : '마이크를 탭하여 시작'} {/* 인식 결과 */} {loading && ( 처리 중... )} {!loading && result && ( {transcript ? 💬 "{transcript}" : null} {result.cmd ? ( {result.cmd} ) : null} {result.response} )} {/* 빠른 명령 버튼 */} ⚡ 빠른 명령 {VOICE_SHORTCUTS.map((s, i) => ( runShortcut(s.command, s.label)} style={S.shortBtn}> {s.icon} {s.label} ))} {/* 사용 이력 */} {history.length > 0 && ( <> 📋 최근 명령 {history.map((h, i) => ( {h.time} {h.text} {h.cmd} ))} )} {/* 도움말 */} 💡 음성 명령 예시 {['"서버 상태 확인해줘"', '"SR 만들어줘"', '"배포 시작해"', '"장애 보고해줘"'].map((e, i) => ( • {e} ))} ) } const S = StyleSheet.create({ root: { flex: 1, backgroundColor: '#f8fafc' }, header: { padding: 20, paddingBottom: 12, backgroundColor: COLORS.gnbBg }, title: { fontSize: 20, fontWeight: '800', color: '#fff' }, subtitle: { fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 4 }, micSection: { alignItems: 'center', paddingVertical: 32, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, micWrap: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#eff6ff', alignItems: 'center', justifyContent: 'center', shadowColor: COLORS.gnbBg, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 12, elevation: 6 }, micHint: { marginTop: 12, fontSize: 13, color: '#64748b' }, resultBox: { margin: 12, borderRadius: 12, padding: 16, alignItems: 'center' }, resultSuccess:{ backgroundColor: '#eff6ff', borderWidth: 1, borderColor: '#bfdbfe' }, resultFail: { backgroundColor: '#fff5f5', borderWidth: 1, borderColor: '#fca5a5' }, transcriptText:{ fontSize: 13, color: '#64748b', marginBottom: 8, fontStyle: 'italic' }, cmdBadge: { backgroundColor: COLORS.gnbBg, borderRadius: 8, paddingHorizontal: 14, paddingVertical: 6, marginBottom: 8 }, cmdText: { color: '#fff', fontWeight: '700', fontSize: 14, fontFamily: 'monospace' }, responseText: { fontSize: 13, color: '#374151', textAlign: 'center' }, sectionTitle: { fontSize: 14, fontWeight: '700', color: '#374151', paddingHorizontal: 16, paddingVertical: 12 }, shortcuts: { flexDirection: 'row', flexWrap: 'wrap', paddingHorizontal: 12, gap: 8 }, shortBtn: { width: '30%', backgroundColor: '#fff', borderRadius: 12, padding: 12, alignItems: 'center', borderWidth: 1, borderColor: COLORS.border, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 3, elevation: 1 }, shortIcon: { fontSize: 24, marginBottom: 4 }, shortLabel: { fontSize: 12, fontWeight: '600', color: COLORS.gnbBg, textAlign: 'center' }, historyBox: { marginHorizontal: 12, backgroundColor: '#fff', borderRadius: 12, borderWidth: 1, borderColor: COLORS.border, overflow: 'hidden' }, historyRow: { flexDirection: 'row', alignItems: 'center', padding: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9' }, historyTime: { fontSize: 11, color: '#94a3b8', width: 40 }, historyText: { flex: 1, fontSize: 12, color: '#374151', marginHorizontal: 8 }, historyCmd: { fontSize: 11, color: COLORS.gnbBg, fontFamily: 'monospace' }, helpBox: { margin: 12, backgroundColor: '#fff', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: COLORS.border }, helpTitle: { fontSize: 13, fontWeight: '700', color: '#374151', marginBottom: 8 }, helpItem: { fontSize: 12, color: '#64748b', marginBottom: 4 }, })