-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvoice_change_helper.go
More file actions
175 lines (150 loc) · 4.87 KB
/
Copy pathvoice_change_helper.go
File metadata and controls
175 lines (150 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (C) 2025-2026 Jose R F Junior <web2ajax@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later
package main
import (
"database/sql"
"fmt"
"log"
"strings"
)
// changeVoice troca a voz da EVA em tempo real
func (s *SignalingServer) changeVoice(client *PCMClient, newVoice string) map[string]interface{} {
log.Printf("🎙️ [VOICE] Solicitação de troca de voz: %s → %s", client.CPF, newVoice)
// Validar se a voz existe
var exists bool
err := s.db.GetConnection().QueryRow(`
SELECT EXISTS(SELECT 1 FROM eva_voices WHERE voice_name = $1 AND is_active = true)
`, newVoice).Scan(&exists)
if err != nil || !exists {
log.Printf("❌ [VOICE] Voz inválida: %s", newVoice)
return map[string]interface{}{
"success": false,
"error": fmt.Sprintf("Voz '%s' não encontrada", newVoice),
}
}
// Obter voz atual
var currentVoice sql.NullString
if err := s.db.GetConnection().QueryRow(`
SELECT preferred_voice FROM idosos WHERE id = $1
`, client.IdosoID).Scan(¤tVoice); err != nil && err != sql.ErrNoRows {
log.Printf("⚠️ [VOICE] Erro ao buscar voz atual: %v", err)
}
oldVoice := "Aoede" // Padrão
if currentVoice.Valid {
oldVoice = currentVoice.String
}
// Atualizar preferência no banco
_, err = s.db.GetConnection().Exec(`
UPDATE idosos SET preferred_voice = $1 WHERE id = $2
`, newVoice, client.IdosoID)
if err != nil {
log.Printf("❌ [VOICE] Erro ao atualizar banco: %v", err)
return map[string]interface{}{
"success": false,
"error": "Erro ao salvar preferência",
}
}
// Registrar histórico
if _, err := s.db.GetConnection().Exec(`
INSERT INTO voice_change_history (idoso_id, old_voice, new_voice, change_method)
VALUES ($1, $2, $3, 'voice_command')
`, client.IdosoID, oldVoice, newVoice); err != nil {
log.Printf("⚠️ [VOICE] Erro ao registrar historico de troca: %v", err)
}
log.Printf("✅ [VOICE] Voz alterada: %s → %s (Idoso %d)", oldVoice, newVoice, client.IdosoID)
// Obter nome amigável da voz
var displayName string
if err := s.db.GetConnection().QueryRow(`
SELECT display_name FROM eva_voices WHERE voice_name = $1
`, newVoice).Scan(&displayName); err != nil {
log.Printf("⚠️ [VOICE] Erro ao buscar display_name: %v", err)
displayName = newVoice // fallback
}
// **CRÍTICO:** A mudança só afeta próxima sessão
// Para mudar EM TEMPO REAL, precisamos reconfigurar Gemini
// Infelizmente, Gemini não suporta mudança de voz mid-session
// Então vamos avisar que mudará na próxima chamada
return map[string]interface{}{
"success": true,
"old_voice": oldVoice,
"new_voice": newVoice,
"display_name": displayName,
"message": fmt.Sprintf("Voz alterada para %s! A mudança será aplicada na próxima conversa.", displayName),
"takes_effect": "next_session",
}
}
// getAvailableVoices retorna lista de vozes disponíveis
func (s *SignalingServer) getAvailableVoices() map[string]interface{} {
rows, err := s.db.GetConnection().Query(`
SELECT voice_name, display_name, gender, tone
FROM eva_voices
WHERE is_active = true
ORDER BY display_name
`)
if err != nil {
return map[string]interface{}{
"success": false,
"error": "Erro ao buscar vozes",
}
}
defer rows.Close()
var voices []map[string]string
for rows.Next() {
var voiceName, displayName, gender, tone string
if err := rows.Scan(&voiceName, &displayName, &gender, &tone); err != nil {
log.Printf("⚠️ [VOICE] Erro ao ler voz: %v", err)
continue
}
voices = append(voices, map[string]string{
"voice_name": voiceName,
"display_name": displayName,
"gender": gender,
"tone": tone,
})
}
if err := rows.Err(); err != nil {
log.Printf("⚠️ [VOICE] Erro na iteração de vozes: %v", err)
}
return map[string]interface{}{
"success": true,
"voices": voices,
"count": len(voices),
}
}
// detectVoiceChangeCommand detecta comando de troca de voz na fala
func detectVoiceChangeCommand(text string) (bool, string) {
textLower := strings.ToLower(text)
// Padrões de comando
patterns := []string{
"troque sua voz",
"mude sua voz",
"troca de voz",
"mudar voz",
"outra voz",
"voz diferente",
}
for _, pattern := range patterns {
if strings.Contains(textLower, pattern) {
// Detectar voz específica mencionada
voiceNames := []string{
// Clássicas
"aoede", "charon", "fenrir", "kore", "puck",
// Novas Femininas
"zephyr", "leda", "callirrhoe", "autonoe", "despina",
"erinome", "laomedeia",
// Novas Masculinas
"orus", "enceladus", "iapetus", "umbriel", "algieba",
"algenib", "rasalgethi", "alnilam",
}
for _, voiceName := range voiceNames {
if strings.Contains(textLower, voiceName) {
// Capitalize primeira letra
return true, strings.Title(voiceName)
}
}
// Sem voz específica: retornar voz aleatória
return true, "" // Vazio = escolher aleatória
}
}
return false, ""
}